API design mistakes will haunt you forever

9/11/2009

The class org.apache.commons.net.ftp.FTPClient from the Apache Commons Net library is a good example of the use of inheritance where composition would have been more suitable.

The class builds on the low-level FTP support provided by its superclass, org.apache.commons.net.ftp.FTP. Its mission, as stated in the Javadocs, reads:

This class takes care of all low level details of interacting with an FTP server and provides a convenient higher level interface.

While that is certainly true, the use of inheritance makes the abstraction very leaky and confusing. When you autocomplete on an FTPClient object in your IDE, you will barely see all the methods starting with an "a", because all the low-level FTP methods are also included, plus the socket-layer level methods inherited from org.apache.commons.net.SocketClient.

This is a classic mistake, probably resulting from coders loving inheritance trees a little too much in the 1990s, before discovering that inheritance makes sense only for "is-a" relationships, while "uses-a" or "has-a" relationships are much more adequately expressed through composition.

If you're designing a library or framework for others to use, keep in mind that you will never ever be able to change anything that you have exposed as part of the public API. This is also the reason why many of Javas APIs seem a little dusty today: java.sql got the exception model totally wrong, and Java's date support was designed before the benefits of immutability were widely accepted. Now, there is no way out without breaking everybody's code.

This, obviously, also applies to some of the older Apache Commons libraries. The only thing you can do is provide a new, improved library, and wait until the old one falls into oblivion.

Integration Testing with Spring and Maven

8/11/2009

Thanks to a Stack Overflow question, I recently stumbled over Spring's integration test support (documentation here). In short, it provides a Spring context of your choice to your jUnit or TestNG classes and adds support for the @Transactional annotation.

However, contrary to the usual behaviour – comitting a transaction if no exception occurred - every transaction is instead rolled back by default, leaving the database unchanged.

All you have to do is to tell JUnit to use Spring's test runner class, tell Spring which context files to load, and add the @Transactional annotation to your class or test methods.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/integrationTestContext.xml"})
@Transactional
public class MyIntegrationTest {

    @Test
    public void myTestMethod() {
        ...
    }

}

This is a rather elegant way of using the IoC pattern for integration testing. It even recycles the Spring container, should you use the same configuration in multiple test cases and/or classes.

However, if you also use Maven, you will have noticed that its opinion on integration testing seems to be "we're not really doing that here". While there is an integration-test phase, there is no distinct place to keep integration tests. While it seems that maybe, some day, there will be support for a "src/it/java" source folder to hold integration test classes, as of now we have some configuration work to do to run unit and integration tests separately.

In our pom.xml, we re-configure the Maven Surefire plugin as follows:

<plugin>
     <artifactId>maven-surefire-plugin</artifactId>
     <configuration>
         <excludes>
             <exclude>**/itest/**</exclude>
         </excludes>
     </configuration>
     <executions>
         <execution>
              <id>surefire-itest</id>
              <phase>integration-test</phase>
              <goals>
                   <goal>test</goal>
              </goals>
              <configuration>
                   <excludes>
                       <exclude>none</exclude>
                   </excludes>
                   <includes>
                       <include>**/itest/**</include>
                   </includes>
              </configuration>
          </execution>
    </executions>
</plugin>

The ugly bit here is that we're forced to keep the integration tests in the same source folder as the unit tests, namely src/test. In this example, we exclude tests that have "itest" somewhere in their package name from unit testing, and bind the surefire plugin to the "integration-test" phase, running only tests with "itest" in their package name. I went for putting all integration tests into a root /itest package, which makes them easy to tell apart, and run separately, from the unit tests.

With this layout, your test classes would, as previously, reside within the same package as the classes that they test, e.g. my.package.MyClassTest, while integration tests reside in their own package, e.g. itest.my.package.MyClassIntegrationTest.

We have now established a working distinction between fast-running unit tests and slower integration tests requiring a Spring container, and possibly additional resources. Certainly not ideal, but doable.