Cet article a fait l'objet d'une traduction automatique.

Visual Studio 2015

Créez de meilleurs logiciels grâce à Smart Unit Tests

Pratap Lakshman

Le monde du développement de logiciels est précipite vers communiqué de jamais-raccourcissement cycles. Le temps où les équipes de développement logiciel pourraient séquencer strictement les fonctions de la spécification, la mise en œuvre et le test dans un modèle de la cascade est expiré depuis longtemps. Développement de logiciels de haute qualité est difficile dans ce monde trépidant et réclame une réévaluation des méthodologies de développement existants.

Pour réduire le nombre de bogues dans un produit logiciel, tous les membres de l'équipe doivent s'entendre sur ce que le système est censé pour faire, et c'est un défi majeur. Spécifications, mise en œuvre et essais généralement arrivé en vase clos, avec aucun moyen commun de communication. Différentes langues ou artefacts utilisés pour chaque rendent difficile pour qu'ils évoluent en cours de l'activité logiciels mise en œuvre, et donc, alors qu'un document de spécification devrait relier le travail de tous les membres de l'équipe, c'est rarement le cas en réalité. La spécification d'origine et de la mise en oeuvre effective peuvent diverger, et la seule chose tout tenant ensemble finalement est le code qui finit par incarnant la spécification ultime et les différentes décisions de conception faites appareillées. Essai tente de réconcilier cette divergence en recourant à l'essai à quelques scénarios de bout à bout bien comprises.

Cette situation peut être améliorée. Un moyen commun pour spécifier que le comportement prévu du système logiciel est nécessaire, celui qui peuvent être partagés sur la conception, de réalisation et de tests, et c'est facile d'évoluer. La spécification doit être directement liée au code, et le support être codifié comme une suite exhaustive de tests. Les techniques basées sur l'outil activés par des Tests unitaires Smart peuvent aider à répondre à ce besoin.

Tests unitaires Smart

Smart Tests unitaires, une fonctionnalité d'aperçu de Visual Studio 2015 (voir Figure 1), est un assistant intelligent pour le développement de logiciels, aidant les équipes de dev trouvez des bugs au début et à réduire les coûts de maintenance de test. Il est basé sur des travaux antérieurs de Microsoft Research, appelée « Pex ». Son moteur utilise des analyses de code de la boîte blanche et contrainte résolvant pour synthétiser les valeurs d'entrée de test précis pour exercer tous les chemins de code dans le code sous test, persistent comme un ensemble compact de tests unitaires traditionnel avec une couverture élevée et évoluer automatiquement la suite de tests, comme le code évolue.

Tests d'unité intelligente de la fait partie intégrante de Visual Studio 2015 Preview
Tests d'unité intelligente de la figure 1 fait partie intégrante de Visual Studio 2015 Preview

En outre et c'est fortement encouragée, propriétés exactitude spécifiées comme assertions dans du code peuvent être utilisées pour mieux orienter génération de scénario de test.

Par défaut, si vous ne faites plus qu'exécuter des Tests unitaires Smart sur un morceau de code, les cas de test générés capture le comportement observé du code sous test pour chacune des valeurs d'entrée synthétisées. À ce stade, sauf dans les cas de test causant des erreurs d'exécution, les autres sont réputés être en passant des tests — après tout, c'est le comportement observé.

En outre, si vous écrivez des affirmations spécifiant les propriétés de l'exactitude du code sous test, puis des Tests unitaires Smart viendra avec test des valeurs d'entrée qui peuvent causer les affirmations à l'échec, aussi bien, chacune telle valeur d'entrée, découvrant un bogue dans le code, et donc un cas de test défaillant. Des Tests unitaires Smart ne peut pas venir avec ces propriétés de justesse par lui-même ; vous écririez eux basé sur votre connaissance du domaine.

Génération de scénario de test

En général, les techniques d'analyse de programme tombent entre ces deux extrêmes suivants :

  • Techniques d'analyse statique de vérifier qu'une propriété est vraie sur tous les chemins d'exécution. Parce que le but est la vérification du programme, ces techniques sont généralement trop prudentes et signaler les violations éventuelles comme erreurs, conduisant à des faux positifs.
  • Techniques d'analyse dynamique vérifier qu'une propriété est vraie sur certains chemins d'exécution. L'essai a été une démarche d'analyse dynamique qui vise à détecter les bogues, mais généralement, il ne justifie pas l'absence d'erreurs. Ainsi, ces techniques souvent ne détecte pas toutes les erreurs.

Il ne serait pas possible de détecter les bogues précisément au moment où appliquer une analyse statique ou employant une technique de test qui est pas au courant de la structure du code. Par exemple, considérez le code suivant :

int Complicated(int x, int y)
{
  if (x == Obfuscate(y))
    throw new RareException();
  return 0;
}
int Obfuscate(int y)
{
  return (100 + y) * 567 % 2347;
}

Techniques d'analyse statique ont tendance à être conservatrice, alors les entiers non linéaire présente en Obfuscate causes des techniques d'analyse statiques plus d'émettre un avertissement concernant une erreur potentielle dans Complicated. Aussi, les techniques de contrôle aléatoires ont très peu de chances de trouver une paire de x et y les valeurs qui déclenche l'exception.

Des Tests unitaires Smart implémente une technique d'analyse qui se situe entre ces deux extrêmes. Tout comme les techniques d'analyse statique, il s'avère qu'une propriété est titulaire pour les chemins plus réalisables. Similaire aux techniques de l'analyse dynamique, il signale uniquement les erreurs réelles et sans faux positifs.

Génération de scénario de test implique ce qui suit :

  • Dynamiquement, découvrant toutes les branches (explicites et implicites) dans le code sous test.
  • Synthèse des valeurs d'entrée de test précis qui exercent ces branches.
  • Enregistrement de la sortie du code sous test pour les entrées de ladite.
  • Ceux-ci comme une suite de tests compact avec une couverture élevée persiste.

La figure 2 montre comment il fonctionne à l'aide de runtime instrumentation et surveillance et ici sont les étapes nécessaires :

  1. Le code sous test est tout d'abord instrumenté et rappels sont plantés qui permettra le moteur de test surveiller l'exécution. Ensuite, le code est exécuté avec la valeur concrète d'entrée plus simple pertinente (selon le type du paramètre). Ceci représente le premier cas de test.
  2. Le moteur de test surveille l'exécution, calcule la couverture pour chaque cas de test et titres comment la valeur d'entrée traverse le code. Si tous les chemins sont couverts, le processus s'arrête ; tous les comportements exceptionnels sont considérés comme des branches, à l'instar de branches explicites dans le code. Si tous les chemins n'ont pas été couverts encore, le moteur de test prend un cas de test qui atteint un point de programme d'où une branche découverte quitte, et détermine comment la condition de branchement dépend de la valeur d'entrée.
  3. Le moteur construit un système de contrainte qui représente la condition sous laquelle contrôle atteint à ce point du programme et continuera ensuite le long de la branche précédemment découverte. Il interroge ensuite un solveur de contraintes afin de synthétiser une nouvelle valeur d'entrée concrète basée sur cette contrainte.
  4. Si le solveur de contraintes permet de déterminer une valeur d'entrée concrète pour la contrainte, le code sous test est exécuté avec la nouvelle valeur d'entrée concrète.
  5. Si la couverture augmente, un cas de test est émis.

Comment la génération de scénario de Test fonctionne sous le capot
Figure 2 Comment la génération de scénario de Test fonctionne sous le capot

Les étapes 2 à 5 sont répétées jusqu'à ce que toutes les branches sont couvertes, ou exploration préconfiguré limites sont dépassées.

Ce processus est appelé une « exploration ». Au sein de l'exploration, le code sous test peut être « exécuté » plusieurs fois. Certaines de ces pistes accroître la couverture, et seulement les passes qui augmentent la couverture émettent des cas de test. Ainsi, tous les tests générés exercent des chemins possibles.

Exploration bornée

Si le code sous test ne contient pas des boucles ou une récursivité sans bornes, exploration généralement s'arrête rapidement car il y a seulement un nombre fini (petit) des chemins d'exécution à analyser. Toutefois, des programmes plus intéressants contiennent-ils des boucles ou une récursivité sans bornes. Dans ce cas, le nombre de chemins d'exécution est (presque) infini, et il est généralement indécidable si une déclaration est accessible. En d'autres termes, une exploration faudrait toujours d'analyser tous les chemins d'exécution du programme. Parce que la génération de test implique en fait courir le code sous test, comment protéger de ce fugitifs exploration ? C'est où exploration bornée joue un rôle clé. Il assure explorations interrompre après un laps de temps raisonnable. Il y a plusieurs limites d'exploration hiérarchisé, configurables qui sont utilisés :

  • Limites du solveur de contraintes limitent la quantité de temps et de mémoire le Solveur peut utiliser dans la recherche de la prochaine valeur d'entrée concrète.
  • Limites du chemin exploration limitent la complexité de la trajectoire de l'exécution étant analysée en termes de nombre de branches prélevées, le nombre de conditions sur les entrées qui doivent être vérifiés et la profondeur de la voie d'exécution en ce qui concerne les frames de pile.
  • Limites de l'exploration limitent le nombre de « passages » qui ne donnent pas un cas de test, le nombre total de pistes autorisées et une limite après laquelle arrêts d'exploration.

Un aspect important de toute approche de test basé sur les outils étant efficace est une rétroaction rapide, et toutes ces limites ont été préconfigurées pour permettre une utilisation rapide interactive.

En outre, le moteur de test utilise des heuristiques pour atteindre une couverture élevée code rapidement en reportant à résoudre des systèmes de contrainte difficile. Vous pouvez laisser le moteur rapidement générer des tests pour le code sur lequel vous travaillez. Cependant, pour s'attaquer à des problèmes de génération d'entrée test dur restant, vous pouvez composer un le seuil de laisser la crise du moteur test davantage sur les systèmes de contrainte compliquée.

Tests d'unités paramétrable

Toutes les techniques d'analyse de programme essaient de valider ou d'infirmer certaines propriétés spécifiées d'un programme donné. Il existe différentes techniques permettant de spécifier les propriétés de programme :

  • API contrats spécifient le comportement des actions individuelles de API du point de vue de la mise en œuvre. Leur objectif est de garantir la robustesse, en ce sens que les opérations ne crash et invariants de données sont conservés. Un problème courant des contrats d'API est leur vision étroite sur les actions individuelles de API, ce qui rend difficile de décrire les protocoles l'échelle du système.
  • Tests unitaires incarnent des scénarios d'utilisation exemplaire du point de vue d'un client de l'API. Leur objectif est de garantir l'exactitude fonctionnel, en ce sens que l'interaction de plusieurs opérations se comporte comme prévu. Un problème commun des tests unitaires, c'est qu'ils sont détachés des détails d'implémentation de l'API.

Des Tests unitaires Smart permet paramétrable tests unitaires, qui unit les deux techniques. Pris en charge par un moteur de génération de test d'entrée, cette méthode combine le client et les perspectives de mise en œuvre. Les propriétés fonctionnelles de la décision correcte (des tests unitaires paramétrée) sont vérifiées sur la plupart des cas de l'application (génération de test d'entrée).

Un test d'unité paramétrable (PUT) est une simple généralisation d'un test unitaire par l'utilisation de paramètres. Un PUT fait des déclarations sur le comportement du code pour tout un ensemble de valeurs d'entrée possibles, au lieu de seulement une valeur d'entrée exemplaire unique. Il exprime des hypothèses sur les entrées de test effectue une séquence d'actions et affirme les propriétés qui devraient occuper dans l'état final ; autrement dit, elle sert de la spécification. Le cahier des charges n'est pas exiger ou introduire toute nouvelle langue ou artefact. Il est écrit au niveau de l'API réels mis en place par le produit logiciel et dans le langage de programmation du produit logiciel. Concepteurs peuvent les utiliser pour exprimer le comportement prévu du logiciel API, les développeurs peuvent les utiliser pour conduire développeur automatisé tests et testeurs peuvent tirer parti pour la génération de test automatique approfondie. Par exemple, l'option PUT suivant affirme que, après avoir ajouté un élément à une liste de non null, l'élément est en effet contenu dans la liste :

void TestAdd(ArrayList list, object element)
{
  PexAssume.IsNotNull(list);
  list.Add(element);
  PexAssert.IsTrue(list.Contains(element));
}

Met séparé les deux préoccupations suivantes :

  1. La spécification des propriétés exactitude du code sous test pour tous les arguments possibles de test.
  2. La réelle « fermé » des cas de test avec les arguments concrets.

Le moteur émet des talons pour la première préoccupation, et vous êtes encouragé à les sortir de la chair, basé sur votre connaissance du domaine. Les appels suivants du Tests unitaires Smart automatiquement générer et mettre à jour des cas de test individuels fermés.

Application

Les équipes de développement de logiciels peuvent être enracinées dans diverses méthodologies déjà, et il est irréaliste de s'attendre à embrasser une nouvelle du jour au lendemain. En effet, des Tests unitaires Smart n'est pas conçu comme un remplacement pour n'importe quelle équipe de pratique essais pourrait être la suite ; au contraire, c'est censé augmenter les pratiques existantes. Adoption est susceptible de commencer par une étreinte progressive, avec des équipes tirant parti des capacités de production et de maintenance de test automatique par défaut tout d'abord et puis de passer à la rédaction des spécifications dans le code.

Comportement observé de test Imaginez avoir à apporter des modifications à un corps de code avec aucune couverture de test. Vous pouvez localiser son comportement en termes d'une suite de tests d'unité avant de commencer, mais qui est plus facile à dire qu'à faire :

  • Le code (code de produit) ne pourrait pas se prête à étant unité testable. Il pourrait avoir des dépendances serrés avec l'environnement extérieur qui devront être isolés, et si vous ne pouvez pas les repérer, vous pourriez savent même pas où commencer.
  • La qualité des tests peut être aussi un problème, et il existe de nombreuses mesures de la qualité. Il est la mesure de couverture — combien de branches, ou chemins de code ou d'autres artefacts de programme, dans le code du produit ne le toucher de tests ? Il y a la mesure des affirmations qui expriment si le code fait la bonne chose. Aucune de ces mesures par eux-mêmes est suffisant, cependant. Au lieu de cela, ce qui serait bien est une haute densité d'assertions en cours de validation avec une couverture de codage élevés. Mais ce n'est pas facile de faire ce genre d'analyses de la qualité dans votre tête que vous écrivez des tests et, en conséquence, vous pourriez vous retrouver avec tests qui exercent les mêmes chemins de code à plusieurs reprises ; peut-être juste tester le chemin"heureux" et vous saurez jamais si le code du produit peut faire face même à tous ces cas limites.
  • Et, désespérément, vous pourriez savent même pas quelles affirmations pour mettre en. Imaginez appelés à apporter des modifications à un code inconnu base !

La capacité de génération de test automatique des Tests unitaires Smart est particulièrement utile dans cette situation. Vous pouvez le courant observé le comportement de votre code comme une suite de tests pour utilisation comme une suite de régression de la base.

Spécification-Based Testing équipes de logiciel peuvent utiliser met ainsi la spécification en génération exhaustive des cas de test drive afin de découvrir les violations des assertions de test. Libérée d'une grande partie du travail manuel nécessaire à écrire des scénarios de test qui atteignent la couverture du code élevé, les équipes peuvent se concentrer sur les tâches des Tests unitaires Smart ne peut pas automatiser, tels qu'écrit des scénarios plus intéressants comme le dit et le développement de l'intégration des tests qui vont au-delà de la portée du met.

Recherche automatique de Bug Assertions exprimant les propriétés de la décision correcte peuvent dire de plusieurs façons : comme faire valoir des déclarations, comme les contrats de code et plus encore. La bonne chose est que ceux-ci sont tous compilés à branches — if instruction avec une branche puis et une autre branche représentant le résultat du prédicat invoqué. Parce que des Tests unitaires Smart calcule les entrées qui exercent toutes les branches, il devient un outil de recherche de bogue efficace, aussi bien — toute entrée il arrive avec que peut déclencher la branche else représente un bogue dans le code sous test. Ainsi, tous les bogues qui sont rapportés sont des bogues réelles.

Réduit les cas de Test entretien en présence de met, un nombre significativement plus faible de cas de tests doivent être maintenus. Dans un monde où les cas de test individuels fermés ont été écrites manuellement, ce qui se passerait lorsque le code sous test évolué ? Vous devrez adapter le code de tous les tests individuellement, qui pourraient représenter un coût important. Mais en écrivant met à la place, seulement le met il faut maintenir. Puis, des Tests unitaires Smart peut générer automatiquement les cas de test individuels.

Défis

Limitations de l'outil la technique d'utilisation des analyses de code boîte blanche avec contrainte résolvant fonctionne très bien sur le code de niveau de l'unité qui a bien isolé. Toutefois, le moteur de test a quelques limitations :

  • Langue : En principe, le moteur de test permet d'analyser des programmes .NET arbitraires, écrits dans un langage .NET. Toutefois, le code de test est généré uniquement en c#.
  • Non-déterminisme : Le moteur de test suppose que le code sous test est déterministe. Si ce n'est pas le cas, elle va tailler des chemins d'exécution non déterministe, ou il pourrait aller en cycles jusqu'à ce qu'il frappe les limites de l'exploration.
  • Simultanéité : Le moteur de test ne gère pas de programmes multithread.
  • Code natif ou code .NET qui n'est pas instrumenté : Le moteur de test ne comprend pas de code natif, c'est-à-dire x 86 instructions appelé par le biais de la fonctionnalité Platform Invoke (P/Invoke) de Microsoft .NET Framework. Le moteur de test ne sait pas comment traduire ces appels dans les contraintes qui peuvent être résolus par un solveur de contraintes. Et même pour le code .NET, le moteur peut analyser uniquement de code qu'il instruments.
  • Virgule flottante : Le moteur de test utilise un solveur de contraintes automatique pour déterminer quelles valeurs sont pertinentes pour le cas de test et le code sous test. Cependant, les capacités du solveur de contraintes sont limitées. En particulier, il ne peut pas raisonner précisément sur la virgule flottante arithmétique.

Dans ces cas, les alertes de moteur test le développeur en émettant un avertissement et le comportement du moteur en présence de telles limitations peut être contrôlé à l'aide d'attributs personnalisés.

Écriture bon paramétrable des Tests unitaires rédaction met bon peut être difficile. Il y a deux questions fondamentales pour répondre :

  • Couverture : Quels sont les bons scénarios (séquences d'appels de méthode) d'exercer le code sous test ?
  • Vérification : Quelles sont les bonnes affirmations qui peuvent s'énoncer facilement sans réimplémenter l'algorithme ?

Une option de vente est utile uniquement si elle apporte des réponses à ces deux questions.

  • Sans une couverture suffisante ; autrement dit, si le scénario est trop étroit pour atteindre tout le code sous test, la mesure de l'option PUT est limitée.
  • Sans vérification suffisante des résultats calculés ; autrement dit, si l'option PUT ne contient pas suffisamment les assertions, il ne peut pas vérifier que le code fait la bonne chose. L'option PUT ne puis fait vérifier que le code sous test n'est pas planter ou avoir des erreurs d'exécution.

Dans les tests d'unités traditionnelles, la série de questions comprend un de plus : Quelles sont les entrées pertinentes test ? Avec le met, cette question est pris en charge par l'outillage. Toutefois, le problème de trouver les bonnes affirmations est plus facile dans les tests d'unités traditionnelles : Les assertions ont tendance à être plus simple, parce qu'elles sont écrites pour les entrées de test particulier.

Jaquette en haut

La fonctionnalité des Tests unitaires Smart Visual Studio 2015 Preview vous permet de spécifier le comportement prévu du logiciel en fonction de son code source, et il utilise l'analyse de code automatisé de boîte blanche en conjonction avec un solveur de contraintes pour générer et maintenir un ensemble compact de tests pertinents avec une couverture élevée pour votre code .NET. Les prestations couvrent les fonctions — concepteurs peuvent les utiliser pour spécifier le comportement prévu des logiciels API ; les développeurs peuvent les utiliser pour conduire automatisé développeur test ; et testeurs peuvent tirer parti pour la génération de test automatique approfondie.

Les cycles de versions de jamais-raccourcissement dans le développement de logiciels est de conduire une grande partie des activités reliées à la planification, spécification, mise en œuvre et des tests pour arriver sans cesse. Ce monde mouvementé nous défie de réévaluer les pratiques existantes autour de ces activités. Bref, fast, libération itérative cycles nécessitent prenant la collaboration entre ces fonctions à un nouveau niveau. Fonctionnalités telles que des Tests unitaires Smart visent à aider les équipes de développement de logiciels plus facilement atteint ces niveaux.


Pratap Lakshman travaille dans la Division développeur chez Microsoft où il est actuellement responsable de programme senior dans l'équipe de Visual Studio , travaillant sur des outils de test.

Grâce à l'expert technique Microsoft suivant d'avoir relu cet article : Nikolai Tillmann