Quelle est la bonne approche pour tester les liaisons Ninject?

Nous utilisons ninject dans tous nos projets et, comme vous le savez sûrement, il devient parfois difficile de vérifier si le kernel est capable de résoudre tous les types au moment de l’exécution, car parfois le contrôle est perdu lorsque ) est haut.

Donc, ce que je demande ici, c’est comment puis-je savoir que mon kernel, après avoir chargé tous les modules et toutes les liaisons, sera capable de résoudre tous les types? Faites-vous une sorte de test unitaire? Ou vous venez de faire des tests d’acceptation de l’application, au moment de l’exécution? Toute suggestion sera géniale 🙂

Ecrivez un test d’intégration qui teste la configuration du conteneur en effectuant une boucle sur tous les types de racine de l’application et en les demandant au conteneur / au kernel. En les demandant au conteneur, vous êtes certain que celui-ci peut créer le graphe d’object complet à votre place.

Un type racine est un type demandé directement à partir du conteneur. La plupart des types ne sont pas des types racines, mais font partie du graphe d’object (car vous devez rarement rappeler le conteneur à partir de l’application). Lorsque vous testez la création d’un type racine, vous testez immédiatement la création de toutes les dépendances de ce type racine, sauf s’il existe des proxies, des usines ou d’autres mécanismes susceptibles de retarder le processus de construction. Cependant, les mécanismes qui retardent le processus de construction pointent vers d’autres objects racines. Vous devriez les identifier et tester leur création.

Évitez d’avoir un test énorme avec un appel au conteneur pour chaque type de racine. Au lieu de cela, chargez (si possible) tous les types de racine à l’aide de la reflection et parcourez-les par itération. En utilisant une sorte de convention sur l’approche de configuration, vous évitez de 1. modifier le test pour chaque nouveau type de racine et 2. d’éviter un test incomplet lorsque vous avez oublié d’append un test pour un nouveau type de racine.

Voici un exemple pour ASP.NET MVC où vos types racine sont des contrôleurs:

[TestMethod] public void CompositionRoot_IntegrationTest() { // Arrange CompositionRoot.Bootstrap(); var mvcAssembly = typeof(HomeController).Assembly; var controllerTypes = from type in mvcAssembly.GetExportedTypes() where typeof(IController).IsAssignableFrom(type) where !type.IsAbstract where !type.IsGenericTypeDefinition where type.Name.EndsWith("Controller") select type; // Act foreach (var controllerType in controllerTypes) { CompositionRoot.GetInstance(controllerType); } } 

METTRE À JOUR

Sebastian Weber a fait un commentaire intéressant auquel je souhaite répondre.

Qu’en est-il de la création d’objects différée à l’aide de fabriques sauvegardées par conteneur (Func) ou générées par des conteneurs (comme l’usine typée de Castle)? Vous ne les attraperez pas avec ce genre de test. Cela vous donnerait un faux sentiment de sécurité.

Mon conseil concerne la vérification de tous les types de racines . Les services créés de manière différée sont en fait des types racines et doivent donc être testés explicitement. Cela vous oblige bien entendu à surveiller de près les modifications apscopes à votre configuration et à append un test lorsque vous détectez l’ajout d’un nouveau type de racine qui ne peut pas être testé à l’aide des tests de sur-configuration par convention déjà en place. Ce n’est pas mauvais, car personne n’a dit que l’utilisation de DI et d’un conteneur de DI signifiait que nous pouvions devenir imprudents tout d’un coup. Il faut de la discipline pour créer un bon logiciel, que vous utilisiez DI ou non.

Bien sûr, cette approche deviendra assez gênante lorsque de nombreuses inscriptions retardent la création. Dans ce cas, il y a probablement quelque chose qui cloche dans la conception de votre application, car l’utilisation de la création différée devrait être l’exception et non la norme. Une autre chose qui pourrait vous causer des problèmes est lorsque votre conteneur vous permet de résoudre des enregistrements Func enregistrés, en les mappant à un délégué () => container.GetInstance() . Cela a l’air sympa, mais cela vous oblige à regarder au-delà de l’enregistrement du conteneur pour rechercher les types de racine et rend beaucoup plus facile d’en manquer un. Étant donné que l’utilisation de la création différée devrait être une exception, il vaut mieux utiliser un enregistrement explicite.

Notez également que même si vous ne pouvez pas tester 100% de votre configuration, cela ne signifie pas que cela rend le test de la configuration inutile. Nous ne pouvons pas tester automatiquement 100% de notre logiciel et devrions prendre particulièrement soin de la partie de notre logiciel / configuration qui ne peut pas être testée automatiquement. Vous pouvez par exemple append des parties indestructibles à un script de test manuel et les tester à la main. Bien sûr, plus vous devez tester à la main, plus vous risquez (et cela ira) mal, vous devriez donc essayer de maximiser la testabilité de votre configuration (comme vous devriez le faire avec tous vos logiciels). Vous obtiendrez bien sûr un faux sentiment de sécurité lorsque vous ne saurez pas quels sont vos tests, mais encore une fois, cela est valable pour tout dans notre profession.

Donc, ce que je demande ici, c’est comment puis-je savoir que mon kernel, après avoir chargé tous les modules et toutes les liaisons, sera capable de résoudre tous les types? Faites-vous une sorte de test unitaire?

Je teste cela en passant en boucle sur chacune des liaisons de mes modules et en vérifiant que le kernel peut leur retourner quelque chose:

 [Test] public void AllModuleBindingsTest() { var kernel = new StandardKernel(new MyNinjectModule()) foreach (var binding in new MyNinjectModule().Bindings) { var result = kernel.Get(binding.Service); Assert.NotNull(result, $"Could not get {binding.Service}"); } }