What I understand about Unit Testing can be expressed using this simple statement "Unit testing is all about the testing in isolation". And that makes the Unit Testing unique from the other form of testing like Integration Testing, Functional Testing or System Testing. I don't see any debate about the goal of Unit testing but when it comes to implementation of the testing and how to isolates the System Under Test (I followed Martin Fowlers naming conventions here) there are lots of differences in opinion and implementation technics. Lets start to talk about this by showing the components of a unit test-
- Unit test class, usually written in JUnit framework
- System Under Test (SUT), the targeted class that I'm gonna unit test and
- Collaborators classes, SUT interacts with those classes to accomplish it's goal
To implement the Unit testing in isolation, today I'll consider three popular approaches and will try to show the comparative advantages and disadvantages from a developer point of view. Those three popular approaches to achieve the isolation are:
- Point cut approach
- Stub approach
- Mock approach
Point cut approach uses the Aspect Oriented Programming (AOP) technique to implement the isolation. The principal behind this is, point cut the collaborator method calls from SUT and eventually return the expected outcome from the method. Usually an Advice class is written that point-cuts the method calls. The advantages of this approach are:
- The SUT is unaware about the point cut and can be configured externally.
- No mock or stub objects need to be written. etc.
The disadvantages are:
- The test data (expected outcome) are stored not in the test class itself.
- Becomes complicated when the point-cut advice needs to provide different response for different test cases.
- The developer needs to be aware of multiple classes and files incase use xml as configuration for point-cut
- Not possible to verify the behavior of the calls
Stub approach stub out the collaborator by injecting the Stubbed collaborators and hence isolate the SUT from having the real collaborators method calls to be invoked. The advantages of this approach are:
- It isolates the SUT from each collaborators replacing with Stub versions hence makes it easy to understand and maintain
- Don't bother about how the method has been implemented i.e. overlooks the implementation detail and looks for the outcome (it is known as state verification)
The disadvantages are:
- It doesn't perform the behavior verifications
Mock approach is more like Stub approach and uses mock objects to isolate the SUT from the collaborators by injecting the Mock collaborator objects. The hallmark difference of Mock approach from the stub approach is that the Mock approach considers the behavior test as well. So the mock approach has all the benefits of Stub approach has and has also the ability to perform the behavior verifications though some people see this one as demerits.
Now see a simple testing implementation in Mock approach using a popular Mock Testing Framework, EasyMock.
public class SampleServiceTest extends TestCase {
private SampleDao daoMock;
private MockControl daoControl;
public static void main(String[] args) {
junit.swingui.TestRunner.run(SampleServiceTest.class);
}
public void setUp() {
daoControl = MockControl.createControl(SampleDao.class);
daoControl.setDefaultMatcher(MockControl.EQUALS_MATCHER);
daoMock = (SampleDao)daoControl.getMock();
}
public void testDeleteSampleData() {
SampleDataId dataId = new SampleDataId(1);
SampleData data = new SampleData(dataId);
SampleDataAudit audit = new SampleDataAudit(sampleData);
daoMock.findData(dataId);
daoControl.setReturnValue(data);
daoMock.evict(data);
daoControl.setVoidCallable();
daoMock.deleteSampleData(dataId);
daoControl.setVoidCallable();
daoMock.insertSampleDataAudit(audit);
daoControl.setVoidCallable();
daoControl.replay();
SampleService csi = new SampleService();
csi.setSampleDao(daoMock);
csi.deleteSampleData(dataId);
daoControl.verify();
}
}