Journée dans la vie d’un développeur DevOps : écrire un nouveau code pour un récit utilisateur
Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2019
Visual Studio 2019 | Visual Studio 2022
Ce tutoriel explique comment vous et votre équipe pouvez tirer le meilleur parti des versions les plus récentes de Team Foundation Version Control (TFVC) et de Visual Studio pour générer votre application. Le tutoriel fournit des exemples d’utilisation de Visual Studio et TFVC pour extraire et mettre à jour du code, suspendre le travail lorsque vous êtes interrompu, demander une révision de code, archiver vos modifications et effectuer d’autres tâches.
Lorsqu’une équipe adopte Visual Studio et TFVC pour gérer son code, elle configure ses ordinateurs serveur et clients, crée un backlog, planifie une itération et effectue les autres tâches de planification nécessaires pour commencer à développer son application.
Les développeurs passent en revue leurs backlogs pour sélectionner les tâches à exécuter. Ils écrivent des tests unitaires pour le code qu’ils planifient de développer. En règle générale, ils exécutent les tests plusieurs fois en une heure, en rédigeant progressivement des tests plus détaillés, puis en écrivant le code qui leur permet de les réussir. Les développeurs discutent souvent de leurs interfaces de code avec des collègues qui utiliseront la méthode qu’ils écrivent.
Les outils Visual Studio Mon travail et Révision du code aident les développeurs à gérer leur travail et à collaborer avec des collègues.
Notes
Les fonctionnalités Mon travail et Révision de code de Visual Studio sont disponibles avec les éditions suivantes :
- Visual Studio 2022 : Visual Studio Community, Visual Studio Professional et Visual Studio Enterprise
- Visual Studio 2019 : Visual Studio Professional et Visual Studio Enterprise
Passez en revue les éléments de travail et préparez-vous à commencer le travail
L’équipe a convenu que, pendant le sprint actuel, vous allez travailler sur Évaluer l’état des factures, un élément prioritaire dans le backlog de produit. Vous décidez de commencer par Implémenter des fonctions mathématiques, une tâche enfant de l’élément de backlog de priorité supérieure.
Dans Visual Studio Team Explorer, dans la page Mon travail, vous faites glisser cette tâche de la liste Éléments de travail disponibles dans la liste Travail en cours.
Pour passer en revue votre backlog et préparer les tâches pour commencer le travail
Dans Team Explorer, si vous n’êtes pas déjà connecté au projet dans lequel vous souhaitez travailler, connectez-vous au projet.
Dans la page Accueil, sélectionnez Mon travail.
Dans la page Mon travail, faites glisser la tâche de la liste Éléments de travail disponibles vers la section Travail en cours.
Vous pouvez également sélectionner la tâche dans la liste Éléments de travail disponibles, puis sélectionner Démarrer.
Brouillon de plan de travail incrémentiel
Vous développez du code en une série de petites étapes. Chaque étape ne prend généralement pas plus d’une heure et peut prendre moins de 10 minutes. Dans chaque étape, vous écrivez un nouveau test unitaire et modifiez le code que vous développez afin qu’il réussisse le nouveau test, en plus des tests que vous avez déjà écrits. Parfois, vous écrivez le nouveau test avant de modifier le code, et parfois vous modifiez le code avant d’écrire le test. Parfois, vous refactorisez. Autrement dit, vous améliorez simplement le code sans ajouter de nouveaux tests. Vous ne modifiez jamais un test qui réussit, sauf si vous décidez qu’il ne représente pas correctement une exigence.
À la fin de chaque petite étape, vous exécutez tous les tests unitaires pertinents pour cette zone du code. Vous ne considérez pas l’étape jusqu’à ce que chaque test réussisse.
Vous n’archivez pas le code vers Azure DevOps Server tant que vous n’avez pas terminé la tâche entière.
Vous pouvez établir un plan approximatif pour cette séquence de petites étapes. Vous savez que les détails et l’ordre exacts des versions ultérieures changeront probablement au fur et à mesure que vous travaillez. Voici la liste initiale des étapes de cette tâche particulière :
- Créez un stub de méthode de test, c’est-à-dire la signature de la méthode.
- Satisfaites un cas typique spécifique.
- Testez une large plage. Assurez-vous que le code répond correctement à une large plage de valeurs.
- Exception en cas de négatif. Traitez correctement les paramètres incorrects.
- Couverture du code. Assurez-vous qu’au moins 80 % du code est exercé par les tests unitaires.
Certains développeurs écrivent ce type de plan dans les commentaires dans leur code de test. D’autres mémorisent simplement leur plan. Il peut être utile d’écrire votre liste d’étapes dans le champ Description de l’élément de travail Tâche. Si vous devez basculer temporairement vers une tâche plus urgente, vous savez où trouver la liste lorsque vous pouvez y revenir.
Créez le premier test unitaire
Commencez par créer un test unitaire. Commencez par le test unitaire, car vous souhaitez écrire un exemple de code qui utilise votre nouvelle classe.
Il s’agit du premier test unitaire pour la bibliothèque de classes que vous testez. Vous créez donc un projet de test unitaire.
- Sélectionnez Fichier>Nouveau projet.
- Dans la boîte de dialogue Créer un nouveau projet, sélectionnez la flèche en regard de Toutes les langues et sélectionnez C#, sélectionnez la flèche en regard de Tous les types de projets, puis choisissez Test, puis sélectionnez Projet de test MSTest.
- Sélectionnez Suivant, puis sélectionnez Créer.
Dans l’éditeur de code, remplacez le contenu de UnitTest1.cs par le code suivant. À ce stade, vous souhaitez simplement illustrer comment l’une de vos nouvelles méthodes sera appelée :
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Fabrikam.Math.UnitTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
// Demonstrates how to call the method.
public void SignatureTest()
{
// Create an instance:
var math = new Fabrikam.Math.LocalMath();
// Get a value to calculate:
double input = 0.0;
// Call the method:
double actualResult = math.SquareRoot(input);
// Use the result:
Assert.AreEqual(0.0, actualResult);
}
}
}
Vous écrivez l’exemple dans une méthode de test, car, au moment où vous écrivez votre code, vous souhaitez que l’exemple fonctionne.
Pour créer un projet de test unitaire et des méthodes
En règle générale, vous créez un nouveau projet de test pour chaque projet testé. Si un projet de test existe déjà, vous pouvez simplement ajouter de nouvelles méthodes et classes de test.
Ce tutoriel utilise l'infrastructure des tests unitaires Visual Studio, mais vous pouvez également utiliser des infrastructures d’autres fournisseurs. L’Explorateur de tests fonctionne également bien avec d’autres infrastructures, à condition d’installer l’adaptateur approprié.
Créez un Projet de test à l’aide des étapes précédentes. Vous pouvez choisir des langages tels que C#, F#et Visual Basic.
Ajoutez vos tests à la classe de test fournie. Chaque test unitaire est une méthode.
Chaque test unitaire doit être préfixé par l’attribut
TestMethod
, et la méthode de test unitaire ne doit pas avoir de paramètres. Vous pouvez utiliser n’importe quel nom souhaité pour une méthode de test unitaire :[TestMethod] public void SignatureTest() {...}
<TestMethod()> Public Sub SignatureTest() ... End Sub
Chaque méthode de test doit appeler une méthode de la classe
Assert
pour indiquer si elle a réussi ou échoué. En règle générale, vous vérifiez que les résultats attendus et réels d’une opération sont égaux :Assert.AreEqual(expectedResult, actualResult);
Assert.AreEqual(expectedResult, actualResult)
Vos méthodes de test peuvent appeler d’autres méthodes ordinaires qui n’ont pas l’attribut
TestMethod
.Vous pouvez organiser vos tests en plusieurs classes. Chaque classe doit être préfixée par l’attribut
TestClass
.[TestClass] public class UnitTest1 { ... }
<TestClass()> Public Class UnitTest1 ... End Class
Pour plus d’informations sur l’écriture de tests unitaires en C++, consultez Écriture de tests unitaires pour C/C++ avec l’infrastructure de tests unitaires Microsoft pour C++.
Créez un stub pour le nouveau code
Ensuite, créez un projet de bibliothèque de classes pour votre nouveau code. Il existe maintenant un projet pour le code en cours de développement et un projet pour les tests unitaires. Ajoutez une référence de projet à partir du projet de test au code en cours de développement.
Dans le nouveau projet, vous ajoutez la nouvelle classe et une version minimale de la méthode qui permettra au moins au test de se générer correctement. La méthode la plus rapide consiste à générer un stub de classe et de méthode à partir de l’invocation dans le test.
public double SquareRoot(double p)
{
throw new NotImplementedException();
}
Pour générer des classes et des méthodes à partir de tests
Tout d’abord, créez le projet dans lequel vous souhaitez ajouter la nouvelle classe, sauf s’il existe déjà.
Pour générer une classe
- Placez le curseur sur un exemple de classe que vous souhaitez générer, par exemple,
LocalMath
, et sélectionnez Actions rapides et refactorisations. - Dans le menu contextuel, choisissez Générer un nouveau type.
- Dans la boîte de dialogue Générer un type, définissez Projet sur le projet de bibliothèque de classes. Dans cet exemple, il s’agit de Fabrikam.Math.
Pour générer une méthode
- Placez le curseur sur un appel à la méthode, par exemple,
SquareRoot
et sélectionnez Actions rapides et refactorisations. - Dans le menu contextuel, choisissez Générer la méthode « SquareRoot ».
Exécutez le premier test
Générez et exécutez le test. Le résultat du test affiche un indicateur rouge Échec et le test apparaît sous la liste des Tests ayant échoué.
Apportez une modification simple au code :
public double SquareRoot(double p)
{
return 0.0;
}
Réexécutez le test et il réussit.
Pour exécuter des tests unitaires
Pour exécuter des tests unitaires :
- Sélectionnez Test>Exécuter tous les tests
- Ou, si l’Explorateur de tests est ouvert, choisissez Exécuter ou Exécuter tous les tests en mode affichage.
Si un test apparaît dans Tests ayant échoué, ouvrez le test, par exemple, en double-cliquant sur le nom. Le point d’échec du test s’affiche dans l’éditeur de code.
Pour afficher la liste complète des tests, choisissez Tout afficher.
Pour afficher les détails d’un résultat de test, sélectionnez le test dans l’Explorateur de tests.
Pour accéder au code d'un test, double-cliquez sur le test dans l'Explorateur de tests ou choisissez Ouvrir un test dans le menu contextuel.
Pour déboguer un test, ouvrez le menu contextuel d’un ou plusieurs tests, puis choisissez Déboguer.
Pour exécuter des tests en arrière-plan chaque fois que vous générez la solution, sélectionnez la flèche en regard de l’icône Paramètres, puis sélectionnez Exécuter les tests après la génération. Les tests qui ont échoué précédemment sont exécutés en premier.
Mettez vous d’accord sur l’interface
Vous pouvez collaborer avec des collègues qui utiliseront votre composant en partageant votre écran. Un collègue pourrait faire remarquer qu’un grand nombre de fonctions passeraient le test précédent. Expliquez que ce test avait pour seul but de s’assurer que le nom et les paramètres de la fonction sont corrects, et que vous pouvez maintenant écrire un test qui capture l’exigence principale de cette fonction.
Vous collaborez avec vos collègues pour écrire le test suivant :
[TestMethod]
public void QuickNonZero()
{
// Create an instance to test:
LocalMath math = new LocalMath();
// Create a test input and expected value:
var expectedResult = 4.0;
var inputValue = expectedResult * expectedResult;
// Run the method:
var actualResult = math.SquareRoot(inputValue);
// Validate the result:
var allowableError = expectedResult/1e6;
Assert.AreEqual(expectedResult, actualResult, allowableError,
"{0} is not within {1} of {2}", actualResult, allowableError, expectedResult);
}
Conseil
Pour cette fonction, vous utilisez le développement basé d’abord sur les tests, dans lequel vous écrivez d’abord le test unitaire pour une fonctionnalité, puis écrivez du code qui satisfait le test. Dans d’autres cas, cette pratique n’est pas réaliste. Vous écrivez donc les tests après avoir écrit le code. Mais il est très important d’écrire des tests unitaires, que ce soit avant ou après le code, car ils conservent la stabilité du code.
Rouge, Vert, Refactorisation...
Suivez un cycle dans lequel vous écrivez à plusieurs reprises un test et confirmez qu’il échoue, écrivez du code que le test réussisse, puis envisagez la refactorisation, c’est-à-dire l’amélioration du code sans modifier les tests.
Rouge
Exécutez tous les tests, y compris le nouveau test que vous avez créé. Après avoir écrit un test, exécutez-le toujours pour vous assurer qu’il échoue avant d’écrire le code qui le fait réussir. Par exemple, si vous oubliez de placer des assertions dans certains tests que vous écrivez, le résultat Échec vous donne l’assurance que lorsque vous le faites réussir, le résultat du test indique correctement qu’une exigence a été satisfaite.
Une autre pratique utile consiste à définir Exécuter des tests après la génération. Cette option exécute les tests en arrière-plan chaque fois que vous générez la solution, afin que vous ayez un rapport continu sur l’état de test de votre code. Vous craignez peut-être que cette pratique puisse ralentir la réponse de Visual Studio, mais cela se produit rarement.
Vert
Écrit votre première tentative au code de la méthode que vous développez :
public class LocalMath
{
public double SquareRoot(double x)
{
double estimate = x;
double previousEstimate = -x;
while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
{
previousEstimate = estimate;
estimate = (estimate * estimate - x) / (2 * estimate);
}
return estimate;
}
Réexécutez les tests et tous les tests réussissent.
Refactorisation
Maintenant que le code effectue sa fonction principale, examinez le code pour trouver des moyens de l’améliorer, ou de le rendre plus facile à modifier à l’avenir. Vous pouvez réduire le nombre de calculs effectués dans la boucle :
public class LocalMath
{
public double SquareRoot(double x)
{
double estimate = x;
double previousEstimate = -x;
while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
{
previousEstimate = estimate;
estimate = (estimate + x / estimate) / 2;
//was: estimate = (estimate * estimate - x) / (2 * estimate);
}
return estimate;
}
Vérifiez que les tests réussissent toujours.
Conseils
Chaque modification que vous apportez lors du développement du code doit être une refactorisation ou une extension :
- La refactorisation signifie que vous ne modifiez pas les tests, car vous n’ajoutez pas de nouvelles fonctionnalités.
- L’extension signifie ajouter des tests et apporter les modifications de code nécessaires pour que les tests existants et les nouveaux tests soient réussis.
Si vous mettez à jour le code existant en fonction des exigences qui ont changé, vous supprimez également les anciens tests qui ne représentent plus les exigences actuelles.
Évitez de modifier les tests qui ont déjà réussi. Au lieu de cela, ajoutez de nouveaux tests. N’écrivez que des tests qui représentent une exigence réelle.
Exécutez les tests après chaque modification.
... et répéter l’opération
Poursuivez votre série d’étapes d’extension et de refactorisation, en utilisant votre liste de petites étapes comme guide approximatif. Vous n’effectuez pas toujours d’étape de refactorisation après chaque extension, et vous effectuez parfois plusieurs étapes de refactorisation successives. Toutefois, vous exécutez toujours les tests unitaires après chaque modification du code.
Parfois, vous ajoutez un test qui ne nécessite aucune modification du code, mais qui renforce votre confiance dans le bon fonctionnement du code. Par exemple, vous souhaitez vous assurer que la fonction fonctionne sur un large éventail d’entrées. Vous écrivez d’autres tests, comme celui-ci :
[TestMethod]
public void SqRtValueRange()
{
LocalMath math = new LocalMath();
for (double expectedResult = 1e-8;
expectedResult < 1e+8;
expectedResult = expectedResult * 3.2)
{
VerifyOneRootValue(math, expectedResult);
}
}
private void VerifyOneRootValue(LocalMath math, double expectedResult)
{
double input = expectedResult * expectedResult;
double actualResult = math.SquareRoot(input);
Assert.AreEqual(expectedResult, actualResult, expectedResult / 1e6);
}
Ce test réussit la première fois qu’il s’exécute.
Pour vous assurer que ce résultat n’est pas une erreur, vous pouvez introduire temporairement une petite erreur dans votre test pour qu’il échoue. Après avoir vu l’échec, vous pouvez le corriger à nouveau.
Conseil
Faites toujours échouer un test avant de le réussir.
Exceptions
Passez maintenant à l’écriture de tests pour des entrées exceptionnelles :
[TestMethod]
public void RootTestNegativeInput()
{
LocalMath math = new LocalMath();
try
{
math.SquareRoot(-10.0);
}
catch (ArgumentOutOfRangeException)
{
return;
}
catch
{
Assert.Fail("Wrong exception on negative input");
return;
}
Assert.Fail("No exception on negative input");
}
Ce test place le code dans une boucle. Vous devez utiliser le bouton Annuler dans Explorateur de tests. Cela met fin au code dans les 10 secondes.
Vous souhaitez vous assurer qu’une boucle sans fin n’a pas pu se produire sur le serveur de build. Bien que le serveur impose un délai d’expiration pour une exécution complète, il s’agit d’un délai d’expiration très long, qui entraînerait des retards importants. Par conséquent, vous pouvez ajouter un délai d’expiration explicite à ce test :
[TestMethod, Timeout(1000)]
public void RootTestNegativeInput()
{...
Le délai d’expiration explicite fait échouer le test.
Mettez à jour le code pour traiter ce cas exceptionnel :
public double SquareRoot(double x)
{
if (x <= 0.0)
{
throw new ArgumentOutOfRangeException();
}
régression ;
Le nouveau test réussit, mais il y a une régression. Un test utilisé pour réussir échoue maintenant :
Recherchez et corrigez l’erreur :
public double SquareRoot(double x)
{
if (x < 0.0) // not <=
{
throw new ArgumentOutOfRangeException();
}
Une fois le problème résolu, tous les tests réussissent :
Conseil
Assurez-vous que tous les tests sont réussis après chaque modification apportée au code.
Couverture du code
À intervalles réguliers au cours de votre travail, puis avant d’archiver le code, obtenez un rapport de couverture du code. Cela montre combien de code a été exercé par vos tests.
Votre équipe vise à couvrir au moins 80 %. Ils assouplissent cette exigence pour le code généré, car il peut être difficile d’obtenir une couverture élevée pour ce type de code.
Une bonne couverture ne garantit pas que toutes les fonctionnalités du composant ont été testées, ni que le code fonctionnera pour chaque plage de valeurs d’entrée. Néanmoins, il existe une corrélation assez étroite entre la couverture des lignes de code et la couverture de l’espace comportemental d’un composant. Par conséquent, une bonne couverture renforce la confiance de l’équipe dans le fait qu’elle teste la plupart des comportements qu’elle devrait tester.
Pour obtenir un rapport de couverture du code, dans le menu Test de Visual Studio, sélectionnez Analyser la couverture du code pour tous les tests . Tous les tests s’exécutent à nouveau.
Lorsque vous développez le total dans le rapport, il indique que le code que vous développez a une couverture complète. Cela est très satisfaisant, car le score important est celui du code testé. Les sections découvertes se trouvent en fait dans les tests eux-mêmes.
En activant le bouton Afficher la couleur de couverture du code, vous pouvez voir quelles parties du code de test n’ont pas été exercées. Le code qui n’a pas été utilisé dans les tests est mis en surbrillance en orange. Toutefois, ces sections ne sont pas importantes pour la couverture, car elles se trouvent dans le code de test et ne sont utilisées que si une erreur est détectée.
Pour vérifier qu’un test spécifique atteint des branches spécifiques du code, vous pouvez définir Afficher la couleur de la couverture du code, puis exécuter le test unique à l’aide de la commande Exécuter du menu contextuel.
Quand avez-vous terminé ?
Vous continuez à mettre à jour le code par petites étapes jusqu’à ce que vous soyez satisfait par les points suivants :
Tous les tests unitaires disponibles réussissent.
Dans un projet avec un très grand ensemble de tests unitaires, il peut être difficile pour un développeur d’attendre que tous les tests s’exécutent. Au lieu de cela, le projet utilise un service d’archivage contrôlé, dans lequel tous les tests automatisés sont exécutés pour chaque jeu de réservations archivé avant qu’il ne soit fusionné dans l’arborescence source. L’archivage est rejeté en cas d’échec de l’exécution. Cela permet aux développeurs d’exécuter un ensemble minimal de tests unitaires sur leurs propres machines, puis de poursuivre d’autres tâches, sans risquer de casser la génération. Pour plus d’informations, consultez Utiliser un processus de génération d’archivage contrôlé pour valider les modifications.
La couverture du code répond à la norme de l’équipe. 75 % est une exigence de projet classique.
Vos tests unitaires simulent tous les aspects du comportement requis, y compris les entrées classiques et exceptionnelles.
Votre code est facile à comprendre et à étendre.
Lorsque tous ces critères sont remplis, vous êtes prêt à vérifier votre code dans le contrôle de code source.
Principes du développement de code avec des tests unitaires
Appliquez les principes suivants lors du développement du code :
- Développez des tests unitaires avec le code et exécutez-les fréquemment pendant le développement. Les tests unitaires représentent la spécification de votre composant.
- Ne modifiez pas les tests unitaires, sauf si les exigences ont changé ou si les tests ont été erronés. Ajoutez progressivement de nouveaux tests à mesure que vous étendez les fonctionnalités du code.
- Veillez à ce qu’au moins 75 % de votre code soit couvert par les tests. Examinez les résultats de la couverture du code à intervalles réguliers et avant d’archiver le code source.
- Vérifiez vos tests unitaires, ainsi que le code, afin qu’ils soient exécutés par les builds continus ou réguliers du serveur.
- Dans la mesure du possible, pour chaque élément de fonctionnalité, écrivez d’abord le test unitaire. Effectuez cette opération avant de développer le code qui le satisfait.
Archivez les modifications
Avant d’archiver les modifications, partagez à nouveau votre écran avec vos collègues afin qu’ils puissent passer en revue de manière informelle et interactive ce que vous avez créé. Les tests continuent d’être au centre de votre discussion avec des collègues qui s’intéressent principalement à ce que fait le code, et non à son fonctionnement. Ces collègues doivent reconnaître que ce que vous avez écrit répond à leurs besoins.
Archivez toutes les modifications que vous avez apportées, y compris les tests et le code, et associez-les aux tâches que vous avez effectuées. L’archivage met en file d’attente le système de génération d’équipe automatisé de l’équipe pour valider vos modifications à l’aide du processus de génération Build CI (intégration continue) de l’équipe. Ce processus de génération aide l’équipe à réduire les erreurs dans son codebase en générant et en testant, dans un environnement propre et distinct de ses ordinateurs de développement, chaque modification apportée par l’équipe.
Vous êtes averti lorsque la génération est terminée. Dans la fenêtre des résultats de la génération, vous voyez que la génération a réussi et que tous les tests ont été passés avec succès.
Pour archiver les modifications
Dans la page Mon travail dans Team Explorer, sélectionnez Archiver.
Dans la page Modifications en attente, vérifiez que :
- Toutes les modifications pertinentes sont répertoriées dans Modifications incluses.
- Tous les éléments de travail pertinents sont répertoriés dans Éléments de travail associés.
Entrez un Commentaire pour aider votre équipe à comprendre le but de ces modifications lorsqu’elle regarde l’historique de gestion de version des fichiers et des dossiers modifiés.
Choisissez Archiver.
Pour intégrer en continu le code
Pour plus d’informations sur la définition d’un processus de build d’intégration continue, consultez Configurer une build CI. Après avoir configuré ce processus de génération, vous pouvez choisir d’être informé des résultats des builds d’équipe.
Pour plus d’informations, consultez Exécuter, surveiller et gérer des builds.