NUnit: Pourquoi Assert.Throws n’attrape pas mon ArgumentNullException?

Je pose cette question à nouveau à la demande du distingué M. John Skeet, qui m’a suggéré d’élaborer un programme de test simple qui isole et démontre le problème que je rencontre et rediffuse la question. Cette question est née de celle-ci , alors pardonnez-moi si cela vous semble très familier. Vous pouvez potentiellement obtenir des détails supplémentaires sur cette question à partir de celle-là.

Le problème que je rencontre concerne Assert.Throws from NUnit 2.5.9. À l’occasion, il ne parviendra pas à intercepter les exceptions lancées dans la méthode invoquée par TestDelegate. J’ai épinglé ce comportement d’une manière reproductible dans le code ci-dessous. (Bien que cela puisse certes être un cas de Fails On My Machine ™.

Pour reproduire l’erreur, j’ai créé une solution avec deux projets DLL C #:

  • Le premier contient une classe, avec une seule méthode publique. Cette méthode est une méthode d’extension qui encapsule la logique requirejse pour créer un SqlCommand , renseigner ses parameters et invoquer ExecuteScalar dessus. Ce projet ne comprend aucune autre référence.
  • La seconde contient une classe unique avec deux méthodes qui testent si la méthode de la première DLL fonctionne comme prévu. Ce projet fait référence à la première et inclut une référence à NUnit Framework. Aucun autre assemblage n’est référencé.

Lorsque j’effectue les tests dans le débogueur, j’observe les éléments suivants:

  1. Assert.Throws appelle correctement la méthode d’extension ExecuteScalar .
  2. Les valeurs de paramètre sont nulles, comme prévu.
  3. ExecuteScalar teste ses parameters pour les valeurs NULL.
  4. Le débogueur exécute et exécute la ligne contenant la throw new ArgumentNullException(...) .
  5. Après l’exécution du throw , le contrôle de l’application n’est pas immédiatement transféré vers Assert.Throws . Au lieu de cela, il continue sur la ligne suivante dans ExecuteScalar .
  6. Dès que la ligne suivante de code est exécutée, le débogueur se brise et affiche l’erreur “L’exception d’argument nul n’a pas été gérée par le code utilisateur”.

Le code source qui isole ce comportement est donné ci-dessous.

LA METHODE D’EXTENSION

 namespace NUnit_Anomaly { using System; using System.Data; using System.Data.SqlClient; public static class Class1 { public static T ExecuteScalar(this SqlConnection connection, ssortingng sql) { if (connection == null) { throw new ArgumentNullException("connection"); } if (sql == null) { throw new ArgumentNullException("sql"); } using (var command = connection.CreateCommand()) { command.CommandType = CommandType.Text; command.CommandText = sql; return (T)command.ExecuteScalar(); } } } } 

LES CAS D’ESSAI

 namespace NUnit_Tests { using System; using System.Data.SqlClient; using System.Diagnostics; using NUnit.Framework; using NUnit_Anomaly; [TestFixture] public class NUnitAnomalyTest { [Test] public void ExecuteDataSetThrowsForNullConnection() { Assert.Throws(() => ((SqlConnection)null).ExecuteScalar(null)); } [Test] public void ExecuteDataSetThrowsForNullSql() { const ssortingng server = "MY-LOCAL-SQL-SERVER"; const ssortingng instance = "staging"; ssortingng connectionSsortingng = Ssortingng.Format("Data Source={0};Initial Catalog={1};Integrated Security=True;", server, instance); using (var connection = new SqlConnection(connectionSsortingng)) { Assert.Throws(() => connection.ExecuteScalar(null)); } } } } 

L’effet net est que les tests échouent alors qu’ils ne le devraient pas. Assert.Throws je Assert.Throws , Assert.Throws devrait capturer mon exception et le test devrait réussir.

METTRE À JOUR

J’ai suivi les conseils de Hans et vérifié la boîte de dialog Exceptions. Je ne cassais pas sur les exceptions jetées , mais je cassais sur les exceptions utilisateur non gérées . Apparemment, c’est la raison pour laquelle le débogueur s’est introduit dans l’IDE ​​lorsque l’exception est levée. En décochant la case, le problème a été résolu et Assert.Throws a relevé. Cependant, si je ne l’ai pas déjà fait, je ne peux pas appuyer simplement sur F5 pour continuer l’exécution, NullReferenceException l’exception deviendra une exception NullReferenceException .

Alors maintenant, la question est la suivante: puis-je configurer des interruptions d’exception pour chaque projet? Je veux seulement faire cela quand je teste, mais pas en général.

En fait, Assert.Throws intercepte votre exception. Toutefois, Visual Studio s’arrête quand même à la première exception. Vous pouvez vérifier cela en appuyant simplement sur F5; Visual Studio sera heureux de continuer à exécuter.

Comme l’assistant d’exception vous l’indique, l’exception n’a pas été gérée par le code utilisateur . Nous soaps donc que Visual Studio ne considère pas NUnit comme un code utilisateur pour une raison quelconque.

entrez la description de l'image ici

Visual Studio vous le dit en clair, si vous savez où regarder:

entrez la description de l'image ici

Il existe également des preuves de ce fait dans la trace de la stack:

entrez la description de l'image ici

Solution 1 : utilisez une version de débogage de NUnit avec des symboles de débogage. Cela obligera Visual Studio à considérer NUnit en tant que code utilisateur et cessera donc de traiter vos exceptions comme “non gérées par le code utilisateur”. Ce n’est pas anodin, mais pourrait fonctionner mieux à long terme.

Solution 2 : désactivez la case à cocher “Activer uniquement mon code” dans les parameters de débogage de Visual Studio:

entrez la description de l'image ici

PS Je n’envisage pas de contournement pour éviter l’utilisation de Assert.Throws complètement, mais il existe bien sûr des moyens de le faire.