Partager via


Effectuer des tests unitaires

Conseil

Ce contenu est un extrait du livre électronique Modèles d’application d’entreprise avec .NET MAUI, disponible dans la .documentation .NET ou en tant que PDF téléchargeable gratuitement qui peut être lu hors connexion.

Miniature de la couverture du livre électronique Modèles d’application d’entreprise avec .NET MAUI.

Les applications multiplateformes rencontrent des problèmes similaires aux applications de bureau et web. Les utilisateurs mobiles diffèrent par leurs appareils, la connectivité réseau, la disponibilité des services et divers autres facteurs. Par conséquent, les applications multiplateformes doivent être testées comme elles seraient utilisées dans le monde réel, afin d’améliorer leur qualité, leur fiabilité et leurs performances. De nombreux types de tests doivent être effectués sur une application, notamment des tests unitaires, des tests d’intégration et des tests d’interface utilisateur. Le test unitaire est la forme la plus courante et essentielle à la création d’applications de haute qualité.

Un test unitaire prend une petite unité de l’application, généralement une méthode, l’isole du reste du code et vérifie qu’elle se comporte comme prévu. Son objectif est de vérifier que chaque unité de fonctionnalité fonctionne comme prévu, afin que les erreurs ne se propagent pas dans l’application. Il est plus efficace de détecter un bogue à l’emplacement où il se produit que d’observer indirectement son effet à un point de défaillance secondaire.

Les tests unitaires ont le plus d’effet sur la qualité du code quand ils font partie intégrante du workflow de développement logiciel. Les tests unitaires peuvent servir de documentation de conception et de spécifications fonctionnelles pour une application. Dès qu’une méthode a été écrite, les tests unitaires doivent être écrits pour vérifier le comportement de la méthode en réponse aux cas de données d’entrée standard, de limite et de données d’entrée incorrectes et vérifier les hypothèses explicites ou implicites du code. Par ailleurs, avec le développement piloté par les tests, les tests unitaires sont écrits avant le code. Pour plus d’informations sur le développement piloté par les tests et sur la façon de l’implémenter, consultez Procédure pas à pas : Développement piloté par les tests à l’aide de l’Explorateur de tests.

Notes

Les tests unitaires sont très efficaces contre la régression. Autrement dit, des fonctionnalités qui fonctionnaient auparavant, mais qui ont été perturbées par une mise à jour défectueuse.

Les tests unitaires utilisent généralement le modèle arrange-act-assert (Organisation, Action, Assertion) :

Étape Description
Réorganiser Initialise les objets et définit la valeur des données transmises à la méthode testée.
Agir Appelle la méthode en cours de test avec les arguments requis.
Assert Vérifie que l’action de la méthode testée se comporte comme prévu.

Ce modèle garantit que les tests unitaires sont lisibles, faciles à comprendre et cohérents.

Injection de dépendances et tests unitaires

L’une des motivations de l’adoption d’une architecture faiblement couplée est qu’elle facilite les tests unitaires. L’interface IAppEnvironmentService est l’un des types inscrits auprès du service d’injection de dépendances. L’exemple de code suivant montre un plan de cette classe :

public class OrderDetailViewModel : ViewModelBase
{
    private IAppEnvironmentService _appEnvironmentService;

    public OrderDetailViewModel(
        IAppEnvironmentService appEnvironmentService,
        IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
        : base(dialogService, navigationService, settingsService)
    {
        _appEnvironmentService = appEnvironmentService;
    }
}

La classe OrderDetailViewModel a une dépendance sur le type IAppEnvironmentService, que le conteneur d’injection de dépendances résout lorsqu’il instancie un objet OrderDetailViewModel. Toutefois, au lieu de créer un objet IAppEnvironmentService qui utilise des serveurs, des appareils et des configurations réels pour tester unitairement la classe OrderDetailViewModel, remplacez plutôt l’objet IAppEnvironmentService par un objet fictif à des fins de tests. Un objet fictif est un objet qui a la même signature qu’un objet ou une interface, mais qui est créé d’une manière spécifique pour faciliter les tests unitaires. Il est souvent utilisé avec l’injection de dépendances pour fournir des implémentations spécifiques d’interfaces pour tester différents scénarios de données et de workflow.

Cette approche permet à l’objet IAppEnvironmentService d’être passé dans la classe OrderDetailViewModel au moment de l’exécution, et dans l’intérêt de la testabilité, permet à une classe fictive d’être passée dans la classe OrderDetailViewModel au moment du test. Le principal avantage de cette approche est qu’elle permet d’exécuter des tests unitaires sans nécessiter de ressources difficiles, comme les fonctionnalités de la plateforme d’exécution, les services web ou les bases de données.

Test des applications MVVM

Le test des modèles et l’affichage des modèles à partir d’applications MVVM sont identiques aux tests de toute autre classe et utilisent les mêmes outils et techniques ; cela inclut des fonctionnalités comme les tests unitaires et les simulations. Toutefois, certains modèles qui sont typiques pour modéliser et afficher des classes de modèle peuvent tirer parti de techniques de test unitaire spécifiques.

Conseil

Testez une chose avec chaque test unitaire. À mesure que la complexité d’un test augmente, la vérification de ce test est plus difficile. En limitant un test unitaire à une seule préoccupation, nous pouvons nous assurer que nos tests sont plus reproductibles, isolés et ont un temps d’exécution plus faible. Consultez meilleures pratiques de test unitaire avec .NET pour obtenir d’autres meilleures pratiques.

Ne soyez pas tenté de faire un exercice de test unitaire sur plusieurs aspects du comportement de l’unité. Cela conduit à des tests difficiles à lire et à mettre à jour. Cela peut également entraîner une confusion lors de l’interprétation d’une défaillance.

L’application multiplateforme eShop utilise MSTest pour effectuer des tests unitaires, qui prend en charge deux types différents de tests unitaires :

Type de test Attribut Description
TestMethod TestMethod Définit la méthode de test réelle à exécuter.
Source de données DataSource Tests qui sont uniquement vrais pour un jeu de données particulier.

Les tests unitaires inclus dans l’application multiplateforme eShop sont TestMethod. Chaque méthode de test unitaire est donc décorée avec l’attribut TestMethod . En plus de MSTest, il existe plusieurs autres frameworks de test disponibles, notamment NUnit et xUnit.

Test des fonctionnalités asynchrones

Lors de l’implémentation du modèle MVVM, les modèles d’affichage appellent généralement des opérations sur les services, souvent de manière asynchrone. Les tests de code qui appellent ces opérations utilisent généralement des services fictifs comme remplacement des services réels. L’exemple de code suivant illustre le test des fonctionnalités asynchrones en passant un service fictif dans un modèle d’affichage :

[TestMethod]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
    // Arrange
    var orderService = new OrderMockService();
    var orderViewModel = new OrderDetailViewModel(orderService);

    // Act
    var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
    await orderViewModel.InitializeAsync(order);

    // Assert
    Assert.IsNotNull(orderViewModel.Order);
}

Ce test unitaire vérifie que la propriété Order de l’instance de OrderDetailViewModel aura une valeur après l’appel de la méthode InitializeAsync. La méthode InitializeAsync est appelée lorsque l’affichage correspondant du modèle d’affichage est ouvert. Pour plus d’informations sur la navigation, voir Navigation.

Lorsque l’instance OrderDetailViewModel est créée, elle s’attend à ce qu’une instance de IOrderService soit spécifiée en tant qu’argument. Toutefois, le OrderService récupère des données à partir d’un service web. Par conséquent, une instance OrderMockService, une version fictive de la classe OrderService, est spécifiée comme argument du constructeur OrderDetailViewModel. Ensuite, les données fictives sont récupérées plutôt que de communiquer avec un service web lorsque la méthode de InitializeAsync du modèle d’affichage, qui utilise des opérations IOrderService, est appelée.

Test des implémentations INotifyPropertyChanged

L’implémentation de l’interface INotifyPropertyChanged permet aux vues de réagir aux modifications qui proviennent des modèles et modèles d’affichage. Ces modifications ne sont pas limitées aux données affichées dans les contrôles. Elles sont également utilisées pour contrôler l’affichage, par exemple les états du modèle d’affichage qui entraînent le démarrage des animations ou la désactivation des contrôles.

Les propriétés qui peuvent être mises à jour directement par le test unitaire peuvent être testées en attachant un gestionnaire d’événements à l’événement PropertyChanged et en vérifiant si l’événement est déclenché après avoir défini une nouvelle valeur pour la propriété. L’exemple de code suivant montre un tel test :

[TestMethod]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
    var invoked = false;
    var orderService = new OrderMockService();
    var orderViewModel = new OrderDetailViewModel(orderService);

    orderViewModel.PropertyChanged += (sender, e) =>
    {
        if (e.PropertyName.Equals("Order"))
            invoked = true;
    };
    var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
    await orderViewModel.InitializeAsync(order);

    Assert.IsTrue(invoked);
}

Ce test unitaire appelle la méthode InitializeAsync de la classe OrderViewModel, ce qui entraîne la mise à jour de sa propriété Order. Le test unitaire réussit, à condition que l’événement PropertyChanged soit déclenché pour la propriété Order.

Test de la communication basée sur les messages

Les modèles d’affichage qui utilisent la classe MessagingCenter pour communiquer entre des classes faiblement couplées peuvent être testés unitairement en s’abonnant au message envoyé par le code testé, comme illustré dans l’exemple de code suivant :

[TestMethod]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
    var messageReceived = false;
    var catalogService = new CatalogMockService();
    var catalogViewModel = new CatalogViewModel(catalogService);

    MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
        this, MessageKeys.AddProduct, (sender, arg) =>
    {
        messageReceived = true;
    });
    catalogViewModel.AddCatalogItemCommand.Execute(null);

    Assert.IsTrue(messageReceived);
}

Ce test unitaire vérifie que le CatalogViewModel publie le message AddProduct en réponse à son AddCatalogItemCommand en cours d’exécution. Étant donné que la classe MessagingCenter prend en charge les abonnements de messages multidiffusion, le test unitaire peut s’abonner au message AddProduct et exécuter un délégué de rappel en réponse à sa réception. Ce délégué de rappel, spécifié en tant qu’expression lambda, définit un champ booléen utilisé par l’instruction Assert pour vérifier le comportement du test.

Gestion des exceptions de test

Des tests unitaires peuvent également être écrits pour vérifier que des exceptions spécifiques sont levées pour des actions ou des entrées non valides, comme illustré dans l’exemple de code suivant :

[TestMethod]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
    var behavior = new MockEventToCommandBehavior
    {
        EventName = "OnItemTapped"
    };
    var listView = new ListView();

    Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}

Ce test unitaire lève une exception, car le contrôle ListView n’a pas d’événement nommé OnItemTapped. La méthode Assert.Throws<T> est une méthode générique où T est le type de l’exception attendue. L’argument passé à la méthode Assert.Throws<T> est une expression lambda qui lève l’exception. Par conséquent, le test unitaire réussit à condition que l’expression lambda lève un ArgumentException.

Conseil

Évitez d’écrire des tests unitaires qui examinent les chaînes de message d’exception. Les chaînes de message d’exception peuvent changer au fil du temps, de sorte que les tests unitaires qui reposent sur leur présence sont considérés comme fragiles.

Test de validation

Il existe deux aspects dans les tests de validation : tester que toutes les règles de validation sont correctement implémentées, et tester que la classe ValidatableObject<T> fonctionne comme prévu.

La logique de validation est souvent simple à tester, car il s’agit généralement d’un processus autonome où la sortie dépend de l’entrée. Il doit y avoir des tests sur les résultats de l’appel de la méthode Validate sur chaque propriété qui a au moins une règle de validation associée, comme illustré dans l’exemple de code suivant :

[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
    var mockViewModel = new MockViewModel();
    mockViewModel.Forename.Value = "John";
    mockViewModel.Surname.Value = "Smith";

    var isValid = mockViewModel.Validate();

    Assert.IsTrue(isValid);
}

Ce test unitaire vérifie que la validation réussit lorsque les deux propriétés ValidatableObject<T> dans l’instance de MockViewModel ont toutes deux des données.

En plus de vérifier que la validation réussit, les tests unitaires de validation doivent également vérifier les valeurs des propriétés Value, IsValid et Errors de chaque instance de ValidatableObject<T>, pour vérifier que la classe s’exécute comme prévu. L’exemple de code suivant illustre un test unitaire qui effectue cela :

[TestMethod]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
    var mockViewModel = new MockViewModel();
    mockViewModel.Forename.Value = "John";

    bool isValid = mockViewModel.Validate();

    Assert.IsFalse(isValid);
    Assert.IsNotNull(mockViewModel.Forename.Value);
    Assert.IsNull(mockViewModel.Surname.Value);
    Assert.IsTrue(mockViewModel.Forename.IsValid);
    Assert.IsFalse(mockViewModel.Surname.IsValid);
    Assert.AreEqual(mockViewModel.Forename.Errors.Count(), 0);
    Assert.AreNotEqual(mockViewModel.Surname.Errors.Count(), 0);
}

Ce test unitaire vérifie que la validation échoue lorsque la propriété Surname de MockViewModel n’a pas de données, et que les propriétés Value, IsValid et Errors de chaque instance ValidatableObject<T> sont correctement définies.

Résumé

Un test unitaire prend une petite unité de l’application, généralement une méthode, l’isole du reste du code et vérifie qu’elle se comporte comme prévu. Son objectif est de vérifier que chaque unité de fonctionnalité fonctionne comme prévu, afin que les erreurs ne se propagent pas dans l’application.

Le comportement d’un objet testé peut être isolé en remplaçant les objets dépendants par des objets fictifs qui simulent le comportement des objets dépendants. Cela permet d’exécuter des tests unitaires sans nécessiter de ressources difficiles, comme les fonctionnalités de la plateforme d’exécution, les services web ou les bases de données

Le test de modèles et de modèles d’affichage à partir d’applications MVVM est identique à celui des autres classes, et les mêmes outils et techniques peuvent être utilisés.