Partager via


Tests unitaires d’applications d’entreprise

Remarque

Ce livre électronique a été publié au printemps 2017 et n’a pas été mis à jour depuis. Il y a beaucoup dans le livre qui reste précieux, mais certains de la matière est obsolète.

Les applications mobiles présentent des problèmes uniques que les applications de bureau et web n’ont pas à se soucier. Les utilisateurs mobiles diffèrent par les appareils qu’ils utilisent, par connectivité réseau, par la disponibilité des services et par un éventail d’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, avec des 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 vérifier que chaque unité de fonctionnalités s’exécute 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’il fait partie intégrante du flux de travail de développement logiciel. 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 standard, limites et cas incorrects de données d’entrée, et qui vérifient les hypothèses explicites ou implicites effectuées 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 agissent en tant que documentation de conception et spécifications fonctionnelles.

Remarque

Les tests unitaires sont très efficaces contre la régression, c’est-à-dire les fonctionnalités utilisées pour fonctionner, 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 organiser de la méthode de test unitaire initialise les objets et définit la valeur des données passées à la méthode sous test.
  • 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 sous test se comporte comme prévu.

Le suivi de 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. L’un des types inscrits avec Autofac est la OrderService classe. 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 sur le 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 une simulation pour les 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 classe au moment du OrderDetailViewModel test. L’avantage principal de cette approche est qu’elle permet aux tests unitaires d’être exécutés sans nécessiter de ressources complexes telles que des services web ou des bases de données.

Test des applications MVVM

Les modèles de test et les modèles d’affichage à partir d’applications MVVM sont identiques aux tests 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, il existe certains modèles qui sont typiques des classes de modèle et d’affichage, 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 prend en charge deux types différents de tests unitaires :

  • 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 et chaque méthode de test unitaire est donc décorée avec l’attribut [Fact] .

Remarque

Les tests xUnit sont exécutés par un exécuteur de test. 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 fictif de la OrderService classe, est spécifiée comme argument du OrderDetailViewModel constructeur. Ensuite, lorsque la méthode du modèle d’affichage InitializeAsync est appelée, qui appelle des opérations, les données fictives sont récupérées IOrderService plutôt que 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, telles que les états du modèle d’affichage qui entraînent le démarrage des animations ou les contrôles à désactiver.

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.

Test de gestion des exceptions

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

Il existe deux aspects pour tester l’implémentation de la validation : tester que toutes les règles de validation sont correctement implémentées et tester 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 vérifier que chaque unité de fonctionnalités s’exécute 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 aux tests unitaires d’être exécutés sans nécessiter de ressources complexes 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.