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.
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.