Découvrir et résoudre les problèmes de mémoire avec l'unité dotMemory de JetBrains

Chris Woodruff

L'unité dotMemory de JetBrains est très flexible et vous permet de contrôler presque tous les aspects de l'utilisation de la mémoire des applications.

Vous pouvez télécharger JetBrains dotMemory sur son site officiel.

Dans ce tutoriel, vous allez apprendre à exploiter dotMemory pour détecter et résoudre les problèmes de mémoire, dans l’exécution de vos applications. Commentez

- Titre : Découvrir et résoudre les problèmes de mémoire avec l'unité dotMemory de JetBrains

- Auteur : Chris Woodruff

- Parution : 4 avril 2019

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2019 Chris Woodruff. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Quand utiliser l'unité dotMemory ?

- III. Comment fonctionne l'unité dotMemory ?

- IV. Exemples d'utilisation de l'unité dotMemory

- IV-A. Vérification des objets

- IV-B. Vérification du trafic mémoire

- IV-C. Comparer des instantanés

- IV-D. Scénarios complexes pour la vérification du trafic mémoire

- V. Utilisation du lanceur autonome

- VI. Analyse des espaces de travail à partir de l'unité dotMemory

- VII. Conclusion

- VIII. A propos de l’auteur

- IX. Remerciements Developpez.com

I - Introduction

Nous savons tous que les tests unitaires sont un aspect important du cycle de programmation et de la vérification du code que nous écrivons dans nos EDI .NET. Nous voulons que nos tests couvrent les plus petits segments de code isolés de nos solutions .NET et couvrent autant de chemins de code que possible afin de nous assurer que notre code se comporte comme prévu.

Parfois, tester la logique du code ne suffit pas. Les profileurs de mémoire ne sont pas des outils que la plupart d’entre nous utilisent tous les jours, mais nous devrions nous en servir plus souvent. Habituellement, nous y avons recours plus tard dans le processus de développement, lorsque les choses tournent mal ou pour corriger des bugs. Si nous pouvions utiliser un profileur de mémoire dans nos tests unitaires, nous pourrions alors avoir l'esprit tranquille en sachant que notre code est non seulement écrit correctement, mais qu’il utilise également la mémoire de manière efficace.

II - Quand utiliser l'unité dotMemory ?

La plupart des développeurs lancent un profileur de mémoire en cas de problème. Nous devrions planifier l'utilisation des profileurs de mémoire tout au long du processus de développement et pas seulement lorsque nous pensons avoir trouvé un bogue impliquant une fuite de mémoire dans notre code. C’est une raison très valable, mais c’est comme traiter un malade avec des médicaments. Pourquoi ne pas anticiper et recourir à des mesures préventives ? Nous devons planifier, faire de l'exercice et faire preuve de discipline pour tester notre code, afin de nous assurer que nos applications consomment et utilisent la mémoire attendue.

Que devrions-nous tester avec dotMemory Unit? Premièrement, nous devrions identifier les zones des applications qui consomment le plus de mémoire. Nous devrions également tester les zones nécessitant de disposer d'un grand nombre d'objets, tels que des boucles impliquant des requêtes. Enfin, nous devrions vérifier la manière dont nos applications utilisent de la mémoire pour le trafic de données. Tous nos tests peuvent enfin avoir leurs instantanés de mémoire sauvegardés à la fin des tests unitaires pour que nous puissions examiner les habitudes et les comportements de consommation.

Nous pouvons également utiliser dotMemory Unit pour tester si certains types d'objets sont instanciés et utilisés dans le code couvert par nos tests unitaires et pour tester les différences entre un instantané enregistré et l’utilisation actuelle de la mémoire.

De nombreux domaines de l’ensemble du processus de développement peuvent tirer parti de l’utilisation des tests unitaires améliorés de dotMemory.

En plus d'utiliser dotMemory Unit pour les tests unitaires, l'outil peut également prendre en charge le besoin de tests fonctionnels et d'intégration au cours du processus de développement.

Voyons maintenant comment nous créons et utilisons ces tests unitaires spéciaux.

III - Comment fonctionne l'unité dotMemory ?

Comment utiliser dotMemory Unit dans nos solutions et projets .NET Framework et .NET Core.

Il existe deux manières d'inclure l'unité dotMemory dans nos solutions .NET.

La première consiste à installer le package dotMemory Unit NuGet dans notre projet. Dans Visual Studio, nous pouvons ajouter le package via la console Package Manager à l'aide de la commande suivante:

Install-Package JetBrains.DotMemoryUnit

Dans l’EDI Rider, nous pouvons ajouter le package JetBrains.DotMemoryUnit à l’aide de la fenêtre NuGet, comme indiqué ci-dessous.

Figure 2: Ajout du package JetBrains.DotMemoryUnit

(cliquez ici pour aggrandir l'image)

Nous pouvons également télécharger les fichiers nécessaires (y compris le lanceur autonome dotMemory) et inclure et référencer les bibliothèques de nos solutions .NET.

Le téléchargement est accessible sur la page d'accueil de l'unité dotMemory: https://www.jetbrains.com/dotmemory/unit/.

Nous pouvons utiliser la plupart des infrastructures de test unitaire du test Runner de ReSharper dans ReSharper et du pilote avec unité dotMemory, y compris xUnit, NUnit et MSTest. Nous pouvons également exécuter nos tests unitaires à partir du dotCoverproduct de JetBrains. Il suffit de comprendre que dotMemory Unit n’est pas un programme d’exécution en test et nous l’utiliserons en coopération avec les frameworks de test d’unité pris en charge.

Les fonctionnalités de dotMemory Unit vont au-delà de l’ajout de nouvelles fonctionnalités dans les tests unitaires. Une fois que les tests unitaires ont été exécutés, nous pouvons enregistrer l’instantané du profil de mémoire, qui peut être utilisé pour la comparaison avec les tests unitaires ultérieurs. Il peut également être ouvert et analysé dans l’outil JetBrains dotMemory. Nous pouvons également exécuter les tests unitaires de notre application créés avec les tests unitaires définis dansdotMemory à partir du fichier exécutable autonome de dotMemory Unit. Cela nous permettra d'inclure nos tests dans nos flux de travail DevOps tels que Continuous Integration (CI) et Continuous Delivery (CD).

Lors de l'exécution de nos tests unitaires, nous disposerons d'options de menu supplémentaires pour exécuter nos tests sous dotMemory Unit.

Figure 3 : Exécution de tests avec dotMemory Unit

(cliquez ici pour aggrandir l'image)

IV - Exemples d'utilisation de l'unité dotMemory

Examinons différentes façons d’utiliser dotMemory Unit dans les tests unitaires que nous effectuons dans nos solutions .NET et .NET Core. Nous allons commencer par quelque chose de simple, puis nous irons plus loin. J'utiliserai la structure xUnit pour mes exemples de tests unitaires.

Remarque : lors de l’utilisation de xUnit, la sortie de dotMemory Unit ne sera pas visible par défaut. Pour la rendre visible, nous devrons lui demander d’utiliser l’assistant de sortie de xUnit dans le constructeur de la classe de test unitaire :

public class AlbumRepositoryTest
{
private readonly AlbumRepository _repo ;

public AlbumRepositoryTest(ITestOutputHelper outputHelper)
{
_repo = new AlbumRepository() ;
DotMemoryUnitTestOutput.SetOutputMethod(
message => outputHelper.WriteLine(message)) ;
}

...
}

IV-A - Vérification des objets

L'un des cas les plus utiles que nous puissions tester est la recherche d'une fuite, en vérifiant dans la mémoire, les objets d'un type spécifique. Nous utilisons ce type de tests pour identifier les problèmes de performances causés par le trafic de mémoire dans nos applications.

Dans l'exemple ci-dessus, nous passons un lambda à la méthode Check (). Cela ne sera fait que si nous exécutons le test à l'aide de l'option « Exécuter les tests unitaires » sous le test dotMemory. L'objet mémoire transmis au lambda contient toutes les données de la mémoire pour le point d'exécution actuel. La méthode GetObjects () renvoie un ensemble d'objets de type « Album » passés dans le deuxième lambda. Enfin, Assert () vérifiera qu'un seul objet de type « Album » a été passé dans l'objet mémoire.

IV-B - Vérification du trafic mémoire

Le test de vérification du trafic mémoire est encore plus simple. Il suffit de marquer le test avec l'attribut <span lang="IT">AssertTraffic </span>. Dans l'exemple ci-dessous, nous affirmons que la quantité de mémoire allouée par tout le code de la méthode DotMemoryTrafficUnitTest () ne dépasse pas 1 000 octets.

[AssertTraffic(AllocatedSizeInBytes = 1000)]
[Fact]
public void DotMemoryTrafficUnitTest()
{
//do some work
}

IV-C - Comparer des instantanés

Nous pouvons utiliser des points de contrôle non seulement pour comparer le trafic, mais également pour d’autres types de comparaisons d’instantanés. Dans l'exemple ci-dessous, nous affirmons qu'aucun objet de l'espace de noms <span lang="NL">Chinook</span> n'a survécu à la récupération de place dans l'intervalle entre memoryCheckPoint1 et le deuxième appel à dotMemory.Check ().

[AssertTraffic(AllocatedSizeInBytes = 1000)]
[Fact]
public async Task DotMemorySnapshot2UnitTest()
{
var repo = new AlbumRepository() ;
var memoryCheckPoint1 = dotMemory.Check() ;
await repo.GetAllAsync() ;

dotMemory.Check(memory=>
{
Assert.Equal(0,memory.GetDifference(memoryCheckPoint1)
.GetSurvivedObjects()
.GetObjects(where => where.Namespace.Like("Chinook")).ObjectsCount) ;
}) ;
}

IV-D - Scénarios complexes pour la vérification du trafic mémoire

Si nous devons obtenir des informations plus complexes sur le trafic mémoire, nous pouvons utiliser une approche similaire à celle du premier exemple. Les lambdas passés à la méthode dotMemory.Check () vérifient que la taille totale des objets implémentant la classe <span lang="PT">Album </span> créée dans l'intervalle entre memoryCheckPoint1 et memoryCheckPoint2 est inférieure à 1 000 octets.

[AssertTraffic(AllocatedSizeInBytes = 1000)]
[Fact]
public async Task DotMemorySnapshotUnitTest()
{
var repo = new AlbumRepository() ;
var memoryCheckPoint1 = dotMemory.Check() ;
await repo.GetAllAsync() ;

var memoryCheckPoint2 = dotMemory.Check(memory=>
{
Assert.True(memory.GetTrafficFrom(memoryCheckPoint1)
.Where(obj => obj.Type.Is<Album>()).AllocatedMemory.ObjectsCount < 1000) ;
}) ;

await repo.GetAllSync() ;

dotMemory.Check(memory =>
{
Assert.True(memory.GetTrafficFrom(memoryCheckPoint2)
.Where(obj => obj.Type.Is<Album>()).AllocatedMemory.ObjectsCount < 1000) ;
}
}

V - Utilisation du lanceur autonome

Que faire si nous ne pouvons pas utiliser ReSharper et Rider en raison de notre lieu de travail ou des outils de développement sélectionnés par notre équipe ? Et si nous voulons exécuter des tests avec un exécuteur de test unitaire autonome (plutôt que Visual Studio ou Rider) ou que les tests de mémoire font partie de nos versions d'intégration continue ? JetBrains a rendu ces cas de figure faciles à gérer ! Nous pouvons utiliser l'exécutable autonome de l'unité dotMemory, l'outil de ligne de commande dotMemoryUnit.exe.

dotMemoryUnit.exe fonctionne comme un médiateur - il lance un programme d'exécution de tests unitaires autonomes et fournit la prise en charge des appels d'unité dotMemory dans les tests en cours.

Dans le cas le plus simple, tout ce que nous avons à faire est de spécifier le chemin de notre unité de test et ses arguments. Dans l'exemple suivant, nous souhaitons exécuter des tests NUnit à partir du fichier MainTests.dll :

dotMemoryUnit.exe "C:\NUnit 3.11.0\bin\nunit-console.exe" -- "E:\MyProject\bin\Release\MainTests.dll"

Par défaut, si l'outil termine son travail avec succès, son code de sortie est 0. Cela n'est pas très pratique lorsque nous exécutons l'outil sur le serveur CI, car nous aurons besoin de savoir s'il existe des tests ayant échoué dans la construction. Dans ce cas, la meilleure option consiste à faire en sorte que dotMemoryUnit.exe renvoie le code de sortie du programme d'exécution de tests unitaires. Pour ce faire, nous devrions utiliser l'argument --propagate-exit-code. Par exemple :

dotMemoryUnit.exe "C:\NUnit 3.11.0\bin\nunit-console.exe" --propagate-exit-code  -- "E:\MyProject\bin\Release\MainTests.dll"

VI - Analyse des espaces de travail à partir de l'unité dotMemory

Les espaces de travail générés et enregistrés par dotMemory Unit peuvent être ouverts avec l’application autonome de JetBrains, dotMemory. L’emplacement par défaut où dotMemory Unit enregistre les fichiers *.dmw est l’emplacement temporaire de notre ordinateur (% temp%). Dans certains cas, nous voudrons peut-être redéfinir l’emplacement des fichiers de l’espace de travail. Cette opération est effectuée à l'aide de DotMemoryUnitAttribute placé devant un assembly, une classe de test ou une méthode de test.

[AssertTraffic(AllocatedSizeInBytes = 1000)]
[DotMemoryUnit(SavingSrategy = SavingStrategy.OnCheckFail, Directory = @"C:\tmp\DotMemoryTrafficUnitTest")]
[Fact]
public void DotMemoryTrafficUnitTest()
{
// do some work
}

Figure 4 : Comparaison des snapshots de mémoiredotMemory

(cliquez ici pour aggrandir l'image)

L'ouverture de dotMemory dans cet exemple montrera les détails des deux instantanés stockés et nous permettra de voir les comparaisons et les différences.

VII - Conclusion

L'unité dotMemory est très flexible et vous permet de contrôler presque tous les aspects de l'utilisation de la mémoire des applications. Utilisez les tests « mémoire » de la même manière que les tests unitaires sur la logique d'application :

- après avoir trouvé manuellement un problème (tel qu'une fuite), écrivez un test de mémoire qui le couvre ;

- préparez des tests proactifs pour vous assurer que les nouvelles fonctionnalités du produit ne créent pas de problèmes de mémoire, tels que des objets laissés en mémoire ou un trafic important.

Merci de m’avoir lu et n’hésitez pas à essayer l’unité dotMemory! C’est gratuit, il vous suffit d’installer Rider, ReSharper ou dotCover.

VIII - A propos de l’auteur

Chris Woodruff plus connu sous le nom de Woody) est diplômé en informatique du College of Engineering de la Michigan State University. Woody développe et conçoit des solutions logicielles depuis plus de 20 ans et a travaillé sur de nombreuses plateformes et outils. C’est un leader de la communauté qui a notamment participé aux événements GRDevNight, GRDevDay, West Michigan Day of .NET et Beer City Code. Il a également joué un rôle déterminant dans l'organisation du célèbre événement Give Camp dans l'Ouest du Michigan, où des professionnels de la technologie mettent leur temps et leur expertise en développement au service des organisations à but non lucratif locales. En tant que conférencier et podcasteur, Woody a abordé divers sujets, notamment la conception de bases de données et l'open source. Il a été un MVP Microsoft dans Visual C#, Data Platform et SQL et a été reconnu en 2010 comme l’un des 20 meilleurs MVP du monde. Woody est Developer Advocate pour  JetBrains et représente JetBrains et les produits .NET, .NET Core en Amérique du Nord.

IX - Remerciements Developpez.com

Developpez.com remercie JetBrains pour l’autorisation de publication de ce tutoriel, initialement publié sur Code Project. Tous nos remerciements aussi à Guillaume SIGUI pour la mise au gabarit et Escartefigue pour la relecture orthographique.

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article