2010-04-14 4 views
44

System.Diagnostics.Contracts.ContractException n'est pas accessible dans mon projet de test. Notez que ce code est purement moi-même déconner avec ma nouvelle copie shiney de Visual Studio, mais je voudrais savoir ce que je fais mal. J'utilise l'édition professionnelle de VS, donc je n'ai pas de vérification statique. Pour continuer à utiliser les contrats de code (ce que j'aime bien), je me suis dit que la seule façon dont ma méthode peut fonctionner est d'attraper l'exception qui est lancée à l'exécution, mais je ne trouve pas cela possible.Comment se fait-il que vous ne puissiez pas attraper les exceptions de contrat de code?

TestMethod

[TestMethod, ExpectedException(typeof(System.Diagnostics.Contracts.ContractException))] 
public void returning_a_value_less_than_one_throws_exception() 
{ 
    var person = new Person(); 
    person.Number(); 
} 

Méthode

public int Number() 
{ 
    Contract.Ensures(Contract.Result<int>() >= 0); 
    return -1; 
} 

erreur

Error 1 'System.Diagnostics.Contracts.ContractException' is inaccessible 
due to its protection level.

Éditer

Après un peu plus de réflexion, je suis arrivé à la conclusion discutée dans les commentaires, ainsi que les suivantes. Étant donné une méthode, si cela avait une exigence qui pourrait être exprimée sous forme de contrat de code, j'écrirais des tests en tant que tels.

[TestMethod] 
[ExpectedException(typeof(ArgumentException))] 
public void value_input_must_be_greater_than_zero() 
{ 
    // Arrange 
    var person = new Person(); 
    // Act 
    person.Number(-1); 
} 

Ceci garantirait que le contrat fait partie du code et ne sera pas supprimé. Cela nécessiterait que le contrat de code lève réellement l'exception spécifiée. Dans certains cas, cela ne serait pas nécessaire.

+1

Je devrais ajouter ceci ne compile pas, naturellement. – Finglas

+0

Je voudrais vraiment voir NUnit déprécier ExpectedExceptionAttribute. [Exception Asserts] (http://www.nunit.org/index.php?p=exceptionAsserts&r=2.10.10) vous donne beaucoup plus de contrôle. – TrueWill

+1

@TrueWill Votre souhait est enfin devenu réalité avec NUnit 3.0: https://github.com/nunit/nunit/wiki/Breaking-Changes#body –

Répondre

65

Ceci est délibéré - bien qu'une légère douleur pour les tests. Le point est que dans le code de production, vous ne devriez jamais vouloir attraper une exception de contrat; cela indique un bogue dans votre code, vous ne devriez donc pas vous attendre à plus que des exceptions inattendues arbitraires que vous pourriez vouloir attraper en haut de votre pile d'appels pour pouvoir passer à la prochaine requête. Fondamentalement, vous ne devriez pas voir les exceptions de contrat comme celles qui peuvent être "traitées" en tant que telles. Maintenant, pour tester c'est douloureux ... mais voulez-vous vraiment tester vos contrats de toute façon? N'est-ce pas un peu comme tester que le compilateur vous empêche de passer dans un string à une méthode qui a un paramètre int? Vous avez déclaré le contrat, il peut être documenté de manière appropriée et appliqué de manière appropriée (en fonction des paramètres, de toute façon).

Si vous faites veulent tester des exceptions du contrat, vous pouvez attraper un Exception nu dans le test et vérifier son nom complet, ou vous pouvez mess avec l'événement Contract.ContractFailed. Je m'attendrais à ce que les cadres de tests unitaires aient un support intégré pour cela au fil du temps - mais il faudra un peu de temps pour y arriver. En attendant, vous voudrez probablement avoir une méthode utilitaire pour vous attendre à une violation de contrat. Une mise en œuvre possible:

const string ContractExceptionName = 
    "System.Diagnostics.Contracts.__ContractsRuntime.ContractException"; 

public static void ExpectContractFailure(Action action) 
{ 
    try 
    { 
     action(); 
     Assert.Fail("Expected contract failure"); 
    } 
    catch (Exception e) 
    { 
     if (e.GetType().FullName != ContractExceptionName) 
     { 
      throw; 
     } 
     // Correct exception was thrown. Fine. 
    } 
} 
+0

Je vois ce que vous voulez dire. Et vous faites un bon point sur le support des tests unitaires à l'avenir. Dans ma méthode exemple, l'utilisation d'un contrat de code est inutile alors? Si c'était une méthode réelle, un code de test/code normal serait plus approprié. – Finglas

+7

@Finglas: Je * personnellement * ne pense pas que les contrats doivent être testés - du moins à moins qu'ils ne soient complexes, et ils ne devraient normalement pas l'être. Cependant, ce n'est qu'une vue personnelle, et vous devriez * absolument * s'attendre à entendre les autres. Pensez à ce que j'ai écrit, mais s'il vous plaît ne vous sentez pas obligé d'accepter :) J'espère que le code dans la réponse vous aidera si vous pensez que vous * voulez * tester le contrat. –

+0

@Jon - non, c'est en fait juste cliqué.Voir: http://stackoverflow.com/questions/1383535/net-4-0-code-contracts-how-will-they-affect-unit-testing La réponse et les commentaires ici ont du sens maintenant que j'ai joué avec CC . Mes tests échoueraient si un contrat n'est pas respecté, alors mes tests passeraient si mon contrat est satisfait et que mon test est satisfait. Cela contraste avec le fait de tester le contrat, ce que je faisais auparavant. – Finglas

2

dans le rtm VS2010, le nom complet a été changé pour "System.Diagnostics.Contracts .__ ContractsRuntime + ContractException".HTH

8

EDIT: J'ai eu une conversion et de ne plus utiliser soit ExpectedException ou cet attribut ci-dessous, mais plutôt avoir un code des méthodes d'extension:

AssertEx.Throws<T>(Action action); 
AssertEx.ThrowsExact<T>(Action action); 
AssertEx.ContractFailure(Action action); 

Ces me permettent d'être plus précis sur l'endroit où la exception est soulevée.

Exemple de méthode ContractFailure:

[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Cannot catch ContractException")] 
    public static void ContractFailure(Action operation) 
    { 
     try 
     { 
      operation(); 
     } 
     catch (Exception ex) 
     { 
      if (ex.GetType().FullName == "System.Diagnostics.Contracts.__ContractsRuntime+ContractException") 
       return; 

      throw; 
     } 

     Assert.Fail("Operation did not result in a code contract failure"); 
    } 

J'ai créé un attribut pour MSTest, qui se comporte de façon similaire au ExpectedExceptionAttribute:

public sealed class ExpectContractFailureAttribute : ExpectedExceptionBaseAttribute 
{ 
    const string ContractExceptionName = "System.Diagnostics.Contracts.__ContractsRuntime+ContractException"; 

    protected override void Verify(Exception exception) 
    { 
     if (exception.GetType().FullName != ContractExceptionName) 
     { 
      base.RethrowIfAssertException(exception); 
      throw new Exception(
       string.Format(
        CultureInfo.InvariantCulture, 
        "Test method {0}.{1} threw exception {2}, but contract exception was expected. Exception message: {3}", 
        base.TestContext.FullyQualifiedTestClassName, 
        base.TestContext.TestName, 
        exception.GetType().FullName, 
        exception.Message 
       ) 
      ); 
     } 
    } 
} 

Et cela peut être utilisé de la même:

Bien que cette question
+0

Pouvez-vous partager du code pour l'extension ContractFailure? – marisks

+0

J'ai édité ma réponse pour montrer cette extension. –

1

vieillit, et une réponse a déjà été fourni, je me sens comme je l'ai une bonne solution qui maintient les choses simples et lisibles. En fin de compte, il nous permet d'écrire des tests sur des conditions préalables aussi simples que:

[Test] 
public void Test() 
{ 
    Assert.That(FailingPrecondition, Violates.Precondition); 
} 

public void FailingPrecondition() { 
    Contracts.Require(false); 
} 

D'accord, donc l'idée est de fournir le code des marchés rewriter avec un contrat personnalisé de classe d'exécution. Cela peut être configuré dans les propriétés sous « Méthodes personnalisées Rewriter » de l'Assemblée (voir le code des marchés section Manuel de l'utilisateur 7.7):

1

Souvenez-vous de vérifier également Call-site Requires Checking!

La classe personnalisée ressemble à quelque chose comme ceci: (! Ne contient rien de fantaisie)

public static class TestFailureMethods 
{ 
    public static void Requires(bool condition, string userMessage, string conditionText) 
    { 
     if (!condition) 
     { 
      throw new PreconditionException(userMessage, conditionText); 
     } 
    } 

    public static void Requires<TException>(bool condition, string userMessage, string conditionText) where TException : Exception 
    { 
     if (!condition) 
     { 
      throw new PreconditionException(userMessage, conditionText, typeof(TException)); 
     } 
    } 
} 

En utilisant une classe personnalisée PreconditionException. Nous ajoutons en outre une petite classe d'aide:

public static class Violates 
{ 
    public static ExactTypeConstraint Precondition => Throws.TypeOf<PreconditionException>(); 
} 

Cela nous permet d'écrire des tests simples, lisibles sur les violations de condition préalable, comme indiqué ci-dessus.

Questions connexes