2009-11-21 4 views
1

Je suis arrivé à quelque chose d'un carrefour. J'ai récemment écrit une application de 10 000 lignes sans TDD (une erreur que je connais). J'ai certainement rencontré un très grand nombre d'erreurs mais maintenant je veux rééquiper le projet. Voici le problème que j'ai rencontré. Prenons un exemple d'une fonction qui fait la division:Comment TDD fonctionne-t-il avec les exceptions et la validation des paramètres?

public int divide (int var1, int var2){ 
if (var1 == 0 || var2 == 0) 
    throw new RuntimeException("One of the parameters is zero"); 
return var1/var2; 
} 

Dans cette situation, je lance une erreur d'exécution afin que je puisse échouer et au moins savoir que mon code est cassé quelque part. La question est 2 fois. Premièrement, est-ce que je fais l'utilisation correcte des exceptions ici? Deuxièmement comment écrire un test pour travailler avec cette exception? Évidemment, je veux qu'il passe le test, mais dans ce cas, il va lancer une exception. Je ne sais pas très bien comment cela fonctionnerait. Existe-t-il une manière différente de traiter ceci avec TDD?

Merci

+1

IllegalArgumentException (ou l'équivalent sinon Java) doit être utilisé au lieu de RuntimeException ordinaire. –

+4

Vous ne pouvez pas convertir TDD en code existant. Vous pouvez écrire des tests unitaires sur du code existant, mais ce n'est pas TDD. TDD est un processus ** design ** dans lequel vous écrivez les tests ** first **. Je suggère d'écrire des tests unitaires pour les interfaces publiques clés de votre code existant, et d'envisager d'utiliser TDD lors de l'ajout de nouvelles classes ou méthodes. – TrueWill

Répondre

6

Pour répondre à votre première question:

S'il est fort probable l'argument dénominateur divide sera de 0 alors vous ne devriez pas utiliser la gestion des exceptions pour intercepter l'erreur. Les exceptions sont coûteuses et ne devraient pas être utilisées pour contrôler le flux du programme. Donc, vous devriez toujours vérifier, mais retourner un code d'erreur (ou utiliser un type nullable comme valeur de retour) et votre code d'appel devrait vérifier cela et le gérer de manière appropriée.

public int? divide (int var1, int var2) 
{ 
    if (var2 == 0) 
    { 
     return null; // Calling method must check for this 
    } 
    return var1/var2; 
} 

Si les zéros sont vraiment l'exception - par ex. il devrait y avoir aucun moyen qu'ils peuvent être passés - alors faites comme vous le faites maintenant.

Pour répondre à votre deuxième question:

Dans vos méthodes d'essai qui vérifient le code d'erreur que vous avez besoin d'un gestionnaire d'exception:

try 
{ 
    divide (1, 0); 
    // If it gets here the test failed 
} 
catch (RuntimeException ex) 
{ 
    // If it gets here the test passed 
} 
+0

Hmm bonne réponse, merci. En ce qui concerne la question 1, est-il toujours préférable de faire en sorte que la fonction appelante valide l'entrée avant de l'envoyer? Peut-être que ce serait nécessaire, peu importe quoi. Hmm ... –

+0

@John - il est préférable de mettre toute votre validation au même endroit, de cette façon si vos besoins changent ou si vous pouvez gérer de nouvelles entrées, il vous suffit de modifier le code à un endroit plutôt que partout. est appelé. – ChrisF

+0

@Chris Hmm okay. C'est un travail difficile, je pense, je dois faire des recherches sur la façon d'accomplir cela. Bon point cependant. Merci pour l'aide –

8

D'abord, votre premier argument (le numérateur) étant nul shouldn probablement » t provoquer une exception à jeter. La réponse devrait juste être zéro. Lancez une exception uniquement lorsqu'un utilisateur tente de diviser par zéro. Deuxièmement, il existe deux façons (en utilisant JUnit) de tester si des exceptions sont levées quand elles devraient l'être. La première méthode « classique »:

@Test 
public void testForExpectedExceptionWithTryCatch() 
     throws Exception { 
    try { 
     divide (1, 0); 
     fail("division by zero should throw an exception!"); 
    } catch (RuntimeException expected) { 
     // this is exactly what you expect so 
     // just ignore it and let the test pass 
    } 
} 

La nouvelle méthode dans JUnit 4 utilise des annotations pour réduire la quantité de code que vous devez écrire:

@Test(expected = RuntimeException.class) 
public void testForExpectedExceptionWithAnnotation() 
     throws Exception { 
    divide (1, 0); 
} 

ici, parce que nous avons ajouté (expected = RuntimeException.class) à la annotation, le test échouera si l'appel à dividene ne lance pas un RuntimeException.

+0

Le deuxième exemple (JUnit4) est concis et fonctionne très bien. Je vous remercie. – 030

0

Je ne réponds pas à votre question principale. Je suggérerais d'utiliser ArgumentException au lieu de RuntimeException.

EDIT: Je suppose .net :)

+0

J'irais même jusqu'à suggérer DivideByZeroException :) – Mathias

0

Votre question était la langue agnostique, alors ma réponse pourrait ne pas appliquer, mais NUnit dans .NET (et je crois JUnit aussi) ont une notation spécifique pour tester les exceptions .En NUnit, votre test ressemblerait à ceci:

[Test] 
[ExpectedException(typeof(RuntimeException))] 
public void DivideByZeroShouldThrow() 
{ 
    divide(1,0); 
} 

Le test échouera si le type d'exception n'est pas levée pendant l'exécution. L'approche try/catch fonctionne aussi, et a ses avantages (vous pouvez localiser exactement où vous attendez que l'exception se produise), mais cela peut s'avérer fastidieux à écrire.

0

ChrisF et Bill the Lizard répondent bien à la première question. Je veux juste ajouter une alternative au test d'exception, avec C++ 11, vous pouvez utiliser un lambda directement dans votre test.

Assert::ExpectException<std::invalid_argument>([] { return divide(1,0); }, "Division by zero should throw an exception."); 

Cela équivaut à:

try 
{ 
divide(1,0); 
Assert::Fail("Division by zero should throw an exception."); 
} 
catch(std::invalid_argument) 
{ 
    //test passed 
}