Monday, September 17, 2007

Redefining the Integration Test

Recently I had been discussing with another developer the distinction between "integration test" and "unit test", and he echoed the same wisdom heard elsewhere. An integration test is any time a test case requires outside resources (i.e. external database, other servers, etc) in order to execute. This is the same definition proffered by a No Fluff Just Stuff conference I had attended a while ago. While I agree that this has been a good rule of thumb, I feel as if it may be time to reexamine this position.

The problem these days its so easy to spin-up resources from within one's test case that I'm not sure that's a fair unit/integration litmus test. Presently, I count libraries like Unitils, DbUnit, and Hypersonic as tools in my testing arsenal. The latter is an in-memory database, and by leveraging it within my test case I eliminate the need to have, for example, MySQL running during test execution.

In many of my tests, I've swapped out use of MySQL with Hypersonic, allowing my test case to be truly autonomous. Using the classic test definition, I've made my test case go from integration to unit test. Merely by changing the spring context file to point to an actual running database, Presto! -- its an integration test again. There's something amiss with the test definition, I feel, when traversing between the two types of test is so easily made.

I'm compelled then to reflect on what unit testing means to me. One of my core beliefs is that unit testing is about isolation -- truly testing only that code which is executed within a class. Take your typical DAO method (excuse the formatting):

public Widget getWidgetByLabel(String label) {
List results = hibernateTemplate.find("select w from Widget w where w.label=?", label);
if(results.isEmpty()) {
throw new WidgetNotFoundException("No widget exists with the label:" + label);
return (Widget) results.get(0); // ignoring too many results for now.


When I put on my unit-testing peering specks, I see very little logic to test here. This is a happy conclusion -- having too much logic within one's DAO is not a good thing. My unit test for this method would merely insure:
  1. The proper arguments are passed to the hibernateTemplate.find() method.
  2. An empty List will trigger an exception.
  3. A non-empty List will make the method return the first object in the list.
However, now that I can easily create an instance of Hypersonic, create the database schema, and initiate a Hibernate session factory, my test can easily verify things like proper Hibernate mapping of my Widget object, and that the HSQL syntax is correct. All this without the necessity of external resources. But these sorts of verifications are about the collaboration -- or integration of other objects and tools.

Therefore, I think I'm revising my personal litmus test. Only when a test case is truly testing in isolation will I consider it a unit test. If a test case involves logic in other classes and libraries, I shall regard these as integration tests, even if it requires no outside resources.

No comments: