Partager via


Tests unitaires des applications d’entreprise

Notes

Ce livre électronique a été publié au printemps 2017 et n’a pas été mis à jour depuis lors. Il y a beaucoup dans le livre qui reste précieux, mais une partie du matériel est obsolète.

Les applications mobiles ont des problèmes uniques dont les applications de bureau et web n’ont pas à se soucier. Les utilisateurs mobiles diffèrent par les appareils qu’ils utilisent, par la connectivité réseau, par la disponibilité des services et par divers autres facteurs. Par conséquent, les applications mobiles doivent être testées, car elles seront utilisées dans le monde réel pour améliorer leur qualité, leur fiabilité et leurs performances. Il existe de nombreux types de tests qui doivent être effectués sur une application, notamment les tests unitaires, les tests d’intégration et les tests d’interface utilisateur, les tests unitaires étant la forme de test la plus courante.

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 case activée que chaque unité de fonctionnalité fonctionne comme prévu, afin que les erreurs ne se propagent pas dans l’application. La détection d’un bogue là où il se produit est plus efficace que l’observation indirecte de l’effet d’un bogue à un point de défaillance secondaire.

Les tests unitaires ont le plus grand effet sur la qualité du code lorsqu’ils font partie intégrante du flux de travail de développement logiciel. Dès qu’une méthode a été écrite, des tests unitaires doivent être écrits pour vérifier le comportement de la méthode en réponse aux données d’entrée standard, limites et incorrectes, et qui case activée toutes les hypothèses explicites ou implicites faites par le code. Sinon, avec le développement piloté par les tests, les tests unitaires sont écrits avant le code. Dans ce scénario, les tests unitaires jouent à la fois le rôle de documentation de conception et de spécifications fonctionnelles.

Notes

Les tests unitaires sont très efficaces contre la régression, c’est-à-dire les 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) :

  • La section arrange de la méthode de test unitaire initialise les objets et définit la valeur des données passées à la méthode testée.
  • La section act appelle la méthode en cours de test avec les arguments requis.
  • La section assert vérifie que l’action de la méthode testée se comporte comme prévu.

Suivre ce modèle garantit que les tests unitaires sont lisibles 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. La classe est l’un des types inscrits auprès d’Autofac OrderService . L’exemple de code suivant montre un plan de cette classe :

public class OrderDetailViewModel : ViewModelBase  
{  
    private IOrderService _ordersService;  

    public OrderDetailViewModel(IOrderService ordersService)  
    {  
        _ordersService = ordersService;  
    }  
    ...  
}

La OrderDetailViewModel classe a une dépendance vis-à-vis du IOrderService type que le conteneur résout lorsqu’il instancie un OrderDetailViewModel objet. Toutefois, au lieu de créer un OrderService objet pour tester unitairement la OrderDetailViewModel classe, remplacez plutôt l’objet OrderService par un fictive pour les besoins des tests. La figure 10-1 illustre cette relation.

Classes qui implémentent l’interface IOrderService

Figure 10-1 : Classes qui implémentent l’interface IOrderService

Cette approche permet à l’objet OrderService d’être passé dans la classe au moment de l’exécution OrderDetailViewModel et, dans l’intérêt de la testabilité, elle permet à la OrderMockService classe d’être passée dans la OrderDetailViewModel classe au moment du test. L’avantage main de cette approche est qu’elle permet d’exécuter des tests unitaires sans nécessiter de ressources complexes telles que des services web ou des bases de données.

Test des applications MVVM

Le test de modèles et d’affichage des modèles à partir d’applications MVVM est identique au test de toutes les autres classes, et les mêmes outils et techniques, tels que les tests unitaires et la simulation, peuvent être utilisés. Toutefois, certains modèles sont typiques pour modéliser et afficher des classes de modèle, qui peuvent tirer parti de techniques de test unitaire spécifiques.

Conseil

Testez une chose avec chaque test unitaire. 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 mobile eShopOnContainers effectue des tests unitaires, qui prennent en charge deux types de tests unitaires différents :

  • Les faits sont des tests qui sont toujours vrais, qui testent des conditions invariantes.
  • Les théories sont des tests qui ne sont vrais que pour un ensemble particulier de données.

Les tests unitaires inclus dans l’application mobile eShopOnContainers sont des tests de faits, de sorte que chaque méthode de test unitaire est décorée avec l’attribut [Fact] .

Notes

Les tests xUnit sont exécutés par un exécuteur de tests. Pour exécuter l’exécuteur de test, exécutez le projet eShopOnContainers.TestRunner pour la plateforme requise.

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 :

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

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

    Assert.NotNull(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 OrderService 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 OrderMockService instance, qui est une version fictive de la OrderService classe, est spécifiée en tant qu’argument du OrderDetailViewModel constructeur. Ensuite, lorsque la méthode du InitializeAsync modèle d’affichage est appelée, qui appelle des opérations, les données fictives sont récupérées IOrderService au lieu de communiquer avec un service web.

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 la vue, comme 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 :

[Fact]  
public async Task SettingOrderPropertyShouldRaisePropertyChanged()  
{  
    bool 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.True(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 :

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

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

    Assert.True(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 boolean champ 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 :

[Fact]  
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 ListView contrôle 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

Le test de l’implémentation de validation comporte deux aspects : le test que toutes les règles de validation sont correctement implémentées et le test que la ValidatableObject<T> classe effectue 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 :

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

    bool isValid = mockViewModel.Validate();  

    Assert.True(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 :

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

    bool isValid = mockViewModel.Validate();  

    Assert.False(isValid);  
    Assert.NotNull(mockViewModel.Forename.Value);  
    Assert.Null(mockViewModel.Surname.Value);  
    Assert.True(mockViewModel.Forename.IsValid);  
    Assert.False(mockViewModel.Surname.IsValid);  
    Assert.Empty(mockViewModel.Forename.Errors);  
    Assert.NotEmpty(mockViewModel.Surname.Errors);  
}

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 case activée 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, telles que des services web ou des 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.