How does TDD work with Exceptions and parameter validation?
I have come to something of a crossroads. I recently wrote a 10,000 line application with no TDD (a mistake I know). I definitely ran into a very large amount of errors but now I want to retrofit the project. Here is the problem I ran into though. Lets take a example of a function that does division:
public int divide (int var1, int var2){
if (var1 == 0 || var2 == 0)
throw new RuntimeException("One of the parameters is zero");
return var1 / var2;
}
In this situation I'm throwing a runtime error so that I can fail and at least find out that my code is broke somewhere. The question is 2 fold. First, am I making the correct use of the exceptions here? Secondly how do I write a test to work with this exception? Obviously I want it to pass the test but in this case it's going to throw an exception. Not too sure how one would work that out. Is there a different way that this is generally handled开发者_开发问答 with TDD?
Thanks
First, your first argument (the numerator) being zero probably shouldn't cause an exception to be thrown. The answer should just be zero. Only throw an exception when a user tries to divide by zero.
Second, there are two ways (using JUnit) to test that exceptions are thrown when they should be. The first "classic" method:
@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
}
}
The newer method in JUnit 4 uses annotations to cut down on the amount of code you need to write:
@Test(expected = RuntimeException.class)
public void testForExpectedExceptionWithAnnotation()
throws Exception {
divide (1, 0);
}
Here, because we added (expected = RuntimeException.class)
to the annotation, the test will fail if the call to divide
doesn't throw a RuntimeException
.
To answer your first question:
If it's quite likely the denominator argument to divide
will be 0 then you shouldn't be using exception handling to trap the error. Exceptions are expensive and shouldn't be used to control program flow. So you should still check, but return an error code (or use a nullable type as the return value) and your calling code should check on this and handle it appropriately.
public int? divide (int var1, int var2)
{
if (var2 == 0)
{
return null; // Calling method must check for this
}
return var1 / var2;
}
If zeros are truly the exception - e.g. there should be no way that they can be passed - then do as you do now.
To answer your second question:
In your test methods that check the failure code you need an exception handler:
try
{
divide (1, 0);
// If it gets here the test failed
}
catch (RuntimeException ex)
{
// If it gets here the test passed
}
I am not answering your main question.
I would suggest using ArgumentException
instead of RuntimeException
.
EDIT: I am assuming .net :)
Your question was language-agnostic, so my answer might not apply, but NUnit in .NET (and I believe JUnit too) have a specific notation for testing exceptions. In NUnit, your test would look like this:
[Test]
[ExpectedException(typeof(RuntimeException))]
public void DivideByZeroShouldThrow()
{
divide(1,0);
}
The test will fail if the right type of exception is not thrown during the execution.
The try/catch approach works too, and has its advantages (you can pinpoint exactly where you expect the exception to occur), but it can end up being pretty tedious to write.
The first question is answered well by ChrisF and Bill the Lizard. I just want to add an alternative to the exception test, with C++11 you can use a lambda directly in your test.
Assert::ExpectException<std::invalid_argument>([] { return divide(1,0); }, "Division by zero should throw an exception.");
This is equivalent to:
try
{
divide(1,0);
Assert::Fail("Division by zero should throw an exception.");
}
catch(std::invalid_argument)
{
//test passed
}
精彩评论