Nous en sommes toujours à rechercher la source du bug qui cause l’apparition d’une ArgumentNullException lors de l’exécution du test unitaire VerifyAddTask dans le fichier T_TodoModel.cs. Il est temps de conclure cette chasse au bug !
Trouvez la cause du bug
Avec une gestion des exceptions et des points d’arrêts, nous en sommes arrivés au code de la méthode InternalAddTask et tentons de déterminer d’où peut venir l’exception :
Au vu de ce code, il va falloir descendre dans l'implémentation de InternalAddTask avec Step Into (F11). Si le test de la première ligne échoue, une InvalidOperationException sera levée mais ce n'est pas l'ArgumentNullException qui est détecté.
Il est donc possible que l'exception soit levée dans le test lui-même lors de l'insertion dans le champ _tasks (qui est une instance de List<TodoTask>).
Comment lever le doute sans exécuter ou descendre dans GetTask ?
On peut déjà vérifier que ni la task reçue, ni le nom passé en paramètre, sont "null" :
En revanche, en passant la souris sur le champ _tasks, on dirait bien que sa valeur est "null" :
Partons donc de cette hypothèse et descendons dans la méthode GetTask avec Step Into (F11) :
L'éditeur indique que _tasks vaut "null" comme nous avions pu le constater dans la méthode appelante. Il est donc maintenant évident que l'appel à FirstOrDefault va échouer parce que la liste sur laquelle cette méthode va opérer sera "null" : nous avons maintenant la raison pour laquelle l'ArgumentNullException va être levée ! ;)
Fixez le problème
Au vu de l'implémentation de la classe TodoModel (pas de constructeur, ni d’initialisation explicite du champs _tasks), on dirait bien que le champ _tasks n'a pas été initialisé. Il suffit donc d'ajouter le code suivant :
Ce n’est pas terminé !
Rappelez-vous : il faut maintenant s'assurer que le fix résout bien le problème initial. Pour cela, il suffit de relancer le test unitaire et de vérifier qu'il passe au vert. Malheureusement, ce n'est pas le cas !
Cette fois-ci, nous tombons sur une exception, mais de type InvalidOperationException. Le message indique qu'il n'est pas possible d'insérer la tâche "sleep" une seconde fois.
Rappelez vous qu'un bug n'est vraiment fixé que lorsque tous les tests sont passés au vert après une correction. Comme ce n'est pas le cas ici, eh bien il faut poursuivre notre enquête...
Démarrez une nouvelle investigation
Comment démarrer cette nouvelle investigation ?
Une première façon est de regarder le code depuis lequel l'exception est levée, et de mettre un point d'arrêt, non pas sur la ligne, mais sur le throw avec un clic-droit ou le raccourci clavier F9 :
Le débogueur arrêtera donc l'exécution juste avant que l'exception ne soit levée.
Si vous vous souvenez du chapitre précédent, vous pourriez aussi avoir envie de mettre un point d’arrêt sur l’appel à CreateTask dans la boucle foreach :
Vous pouvez aussi définir un point d’arrêt conditionnel de type Hit Count pour mettre en pause avant l’exécution de la quatrième itération (c’est-à-dire avant que la seconde tâche avec le nom "sleep" ne soit ajoutée) :
Lorsqu'on relance le test unitaire, on s'aperçoit que l'exception est levée s'il existe une tâche avec le même nom, comme on pouvait s'en douter. Il faut donc décider s'il faut conserver cette fonctionnalité ou modifier le test unitaire, voire en ajouter un autre pour "officialiser" le comportement attendu.
Résumé
Comme vous pouvez le constater, une investigation peut nécessiter plusieurs itérations de recherche de bug. Surtout, gardez à l'esprit que plusieurs issues sont possibles la plupart du temps. Je vous conseille cependant de privilégier l’ajout de tests unitaires qui rendent les comportements des composants testés beaucoup plus explicites !
La partie suivante de ce cours sur le débogage d’applications C# vous donnera de nouveaux moyens de visualiser et même de modifier les données manipulées par le code. Cela vous rendra plus efficace et vous aidera à mieux comprendre le code que vous serez amenés à maintenir… sans que vous l’ayez écrit. :p