Sunday, August 10, 2008

Unit Testing in Software Development and Junit

Late adoption of certain practices mean that you talk about a technology or a process as something that deserves a second look, whereas, the industry around you considers it a 'norm'. In my case such is the case with incorporating unit testing into development process.

Sometimes one gets too cosy with their SystemOutPrints or ResponeWrites for testing software, or use log4j (well, i've another story for log4j's late adoption) for verifying tests, that one wonders, dosent't 'log' in log4j stand for some kind of logging, and not testing.

Lets see, when we talk about testing, we talk about a piece of functionality, encapsulated in a single method, or a sequence of method calls behaving as desired. So, when we test, we know the outcome, and we want to verify that the outcome of an actual run is as it is expected. A SystemOutPrint or a log command, may serve this purpose but there has to be a 'human' sitting on a terminal reading the output to verify that. Lets say in a system with 10 classes with 10 methods each, there are 100 calls to verify. How can that be automated?

Before going into unit testing with junit, let me quantify what's wrong with using SystemOutPrint (SOP going forward) or log4j for testing. With objections out of the way, we can discuss how junit addresses such instances.

1. clutter
Putting SOP's clutter your code with statements one has to remove after the code is 'tested'. In many instances, a developer either does not remove them or keeps them commented, for 'just in case I need to test it again'. So, your console output from application is cluttered with unwanted output and your code is cluttered with unwanted lines of code. Not to mention, when a developer wants to test again, he/she has to either type in SOP's again or uncomment the existing SOP's..

2. misuse
We all agree that log4j is for logging purposes. And SOP is more generic in use, and cannot be just tied to logging. SOP's have a place and logging has a place. But their place is not for testing. They can be utilized alongside testing, for logging and printing test results to console, but they are not test tools. Testing should be executed and the system should tell you if all the tests succeeded, irrespective of a person parsing 3000 lines in a log file trying to figure out the test results.

3. non-repeatability
When a developer tests using log statements, it is tested when it is coded. Seems like a redundant statement. But read on with me. Lets say a piece of functionality is tested and deployed using log statements. Another developer comes along and modifies your code to add more functionality. He/She tests his/her work using logs and is satisfied with his/her results.. You can't disagree with me for the possibility of a bug being introduced in your original piece of work. If such a bug gets introduced, who tests for that? and when? So, tests should be repeatable, so if any change occurs in your code or else where, it does not affect your original coded behaviour.

4. undocumented
Since using SOP's and logs are left at the needs/desires of an individual developer, there is no need or lack thereof to use or not use them. Accordingly, a developer new to the team or team that inherits an application has no clue what is tested and what is not. Also, all the SOP's that made sense to one developer or a team is 'noise' to another developer or team.

5. non-intutive
If a system is tested using SOP's and logs how can one answer this simple question : How do I test this thing? You can't.. A person doing the testing has to have intimate knowledge of the code and has to know what to look for in the logs to test such systems. Completely impractical. Wouldn't it be easy to just answer 'hey run the 'test' target in ant' or 'run maven and don't skip the test' or something like that.. And your build tool runs all the tests and gives you the results..

I think these 5 points convey my point. So, how can these issues be addressed. Let me explain using a junit example.

Lets begin with a class that we want to test. Please keep in mind, that the example will be really simple, so as to convey the point. You can read more documentation at junits website.(*1)

public class Doodle {
public int add(int a, int b) {
return a+b;
}
}

The Class Doodle has a method add which adds two numbers. We need to make sure it adds correctly. (One might say, a+b should add.. what's there to test? yes, I know.. this is just an example.. pretend for a moment that the implementation is pretty complex and needs testing..)

How do we go about testing this without using the dreaded SOP's and log statements.

We write a test class. Here is how it looks.



import org.junit.Test;
import static org.junit.Assert.*;

public class TestDoodle {
private Doodle doodle;

@Before
public void setUp() {
doodle = new Doodle();
}

@Test
public void testDoodleAdd() throws Exception {
int result = d.add(2, 2);
assertEquals(4, result);
}

@After
public void cleanUp() {
doodle = null;
}
}

We have a TestDoodle class, with three methods.
setUp() - Its annotated with @Before, which tells junit to run this method before every test method
testDoodleAdd() - Its annotated with @Test, which tells junit to run this method when this class is invoked for testing
cleanUp() - Its annotated with @After, which tells junit to run this method after each test method.


In testDoodleAdd(), we invoke our add method by sending, 2 and 2 for test values, and we get the results back in result variable, which should be 4.
As mentioned earlier, I need to know what the result is before running the test, to make sure that the test is successful. That is happening in the junit method assertEquals(expectedValue, actualValue). If this method returns 'true' then the test is successful, and if not the test is a failure.

How do we run it. It can run on a command line, but I'll give an 'ant' snippet.

<target name="test" depends="compile">
<junit fork="yes" haltonfailure="yes">
<test name="TestDoodle" />
<formatter type="plain" usefile="false" />
<classpath refid="tests.path" />
</junit>
</target>

So, the test can be run by running 'ant test' on command line. (note, this snippet depends on 'compile' task, and 'tests.path' value, which are not included. but I think the intent is clear)

Now, let us see how it stands for the above-mentioned objections to using SOP's and logs.

1. no clutter
As it is evident, Doodle.java is clutter free.

2. no misuse
There is no unnecessary use of SOP's and log statements for other than their intended purposes. (I know, there are none in the example, but I could log the add operation for logging purposes.. not for testing..)

3. repeatable
This test can be run any number of times, without any side-effect. Each time the class is modified, this test, in addition to any new test can be run. If any of the results change due to code changes, it will manifest itself next time the test runs.

4. documented
There is a TestDoodle class, and its purpose is to test the Doolde class. And, this class along with its annotations, and comments (if any) is clearly a clean way of understanding which tests are being performed and what the expected results are.

5. intutive
Ok, I know a person has to know junit is being used, and in my case 'ant' is used to run tests.. But most of that is 'staple' for java development nowadays. It is very unlikely any experienced java developer will not know what is going on after looking at the ant's build.xml file and corresponding test classes. So, it is very intutive by convention.

In summary, incorporating unit tests in a development process is necessary for many other reasons other than the general concept of testing. And if junit makes it easy, why not use it.

(*1) http://www.junit.org/

No comments:

Post a Comment