in fewer lines of code than in several lines of code. We have sung the praises for those that promote
fluency and ease.
However, aiming for fewer lines of code is not a good thing always. The question to
ask is, if the code is merely terse or is it concise.
Terse code, just like concise code, is small. But, terse code is treacherous.
It can easily elude you by either making it hard to follow and/or make it incorrect.
Consider the following Java/JUnit test example (you can use similar example with C# and NUnit
as well—the problem is the same).
@Test
public void convertCtoF()
{
_converter.from(TemperatureUnit.C);
_converter.to(TemperatureUnit.F);
Assert.assertEquals(50.0, _converter.convert(10), 0);
}
ConvertCtoF() is a test method. JUnit 4.0 promotes conciseness—you did not have to inherit the test class from
a TestCase base class and that is good. The above code tests the convert() method of a TemperatureConverter class.
Now, suppose I want to write an exception test to indicate that the convert() method of
the TemperatureConverter will fail if input value if too large. Here is test for that:
@Test
public void convertCtoFFailureForLargeValue()
{
_converter.from(TemperatureUnit.C);
_converter.to(TemperatureUnit.F);
try
{
_converter.convert(Double.MAX_VALUE);
Assert.fail("Expected
exception due to overflow");
}
catch(ConverterException
ex)
{
Assert.assertTrue(true); //
:) Success
}
}
As an astute reader familiar with JUnit 4.0, you'd point out that I have not taken advantage of expressive
syntax to perform exception test. Rather than writing the clumsy try/catch block with a call to the fail()
method, I should've used the annotation that indicates the desired exception? Let's see how that would look:
@Test(expected=ConverterException.class)
public void convertCtoFFailureForLargeValueLite()
{
_converter.from(TemperatureUnit.C);
_converter.to(TemperatureUnit.C);
_converter.convert(Double.MAX_VALUE);
}
ConverterException was thrown. The code size is certainly smaller, but this is an example
of terse code and it is wrong—it is not equivalent to the previous longer version. Sadly,
JUnit 4.0 also promotes terseness.
This example illustrates why I am not a big fan of annotation based exception testing and
why I consider it to be terse rather than being concise.
Even though the test passed, it passed for the wrong reason. It passed because the method
to() threw the exception in this case (because I inadvertently set the same unit for conversion
for from and to), and the test did not even reach the convert()method.
@Test(expected=ConverterException.class) is terse and lacks the granularity that the try/catch block
provides. It does not tell you which method within the test actually threw the exception you're
expecting. This is the same problem with the [ExpectedException(...)] attribute in C# (NUnit and
VS testing tool)—it is terse and lacks graularity.
If you are using Java or C# to test your code, I say the longer verbose version is actually better
than the terse version.
If you are using Groovy or JRuby to test your Java code (or plan to use IronRuby to test
your C# code), you can enjoy a concise syntax to write this test.
Here is a Groovy example to test the TemperatureConverter:
void testConvertCtoFFailureForLargeValueConcise()
{
_converter.from(TemperatureUnit.C);
_converter.to(TemperatureUnit.F);
shouldFail {_converter.convert(Double.MAX_VALUE);}
}
shouldFail() method. If the from() or the to() method were to thrown an exception, the test will
fail. Also, if the convert() method does not throw an exception, the test will fail. The way this test
is written, you are expecting the convert() method to throw an exception. You can be precise about
which exception you expect as follows:
shouldFail
ConverterException, {_converter.convert(Double.MAX_VALUE);}
exception it expects, and the second is the closure block that contains the code that is expected to
throw the mentioned exception.
While the annotation based exception testing illustrates terseness, the closure based approach
shows conciseness. Both let you type fewer lines of code, but one is elusive while the other is expressive.
When you come across APIs, evaluate whether they are terse or concise. Prefer conciseness over terseness.