Unit Testing Package Dependencies

As I mentioned in my last blog entry, not many developers seem to care about Java package dependencies. I’ve certainly worked on several projects where package cycles would sneak into the application, much to my frustration.

Whenever I’ve complained about this, people would look at me and say, “Oh, I didn’t notice that a cycle had happened.” To me, if you change a class’s dependencies, you change its responsibility. And when a developer doesn’t notice that the responsibility has changed significantly, I wonder about the developer’s object-oriented design skill.

But at the end of the day, what I want is a strategy to solve this problem. Again and again, I seem to come back to one powerful strategy: unit tests. When I, as an architect on a team, say that we shouldn’t have any cyclical package dependencies, I’m stating a technical requirement. And since there’s a technical requirement, one way to ensure that the requirement remains in the code is to write a unit test for that requirement.

Here’s a simple unit test, making use of the JDepend tool.

package ca.intelliware.jdepend;

import java.util.Collection;
import java.util.Properties;

import jdepend.framework.JDepend;
import jdepend.framework.PackageFilter;
import jdepend.framework.PropertyConfigurator;
import junit.framework.TestCase;

public class JDependTest extends TestCase {

  public void testDependencies() throws Exception {

    Properties properties = new Properties();
    properties.put("ignore.java", "java.*,javax.*,org.w3c.*,org.xml.*");
    properties.put("ignore.sun", "sun.*,com.sun.*");
    properties.put("analyzeInnerClasses","true");
    PropertyConfigurator config = new PropertyConfigurator(properties);

    Collection configuredPackages = config.getFilteredPackages();
    JDepend jDepend = new JDepend(
        new PackageFilter(configuredPackages));
    jDepend.addDirectory("../myProject/bin/");

    jDepend.analyze();

    assertFalse("contains cycles", jDepend.containsCycles());
  }
}

One thing that we have been able to depend upon is that developers know to run all the unit tests before integrating their code into the repository. If a developer introduces a cycle. The unit tests won’t pass, and the developer gets feedback that a technical requirement has been violated.

I can add some additional assertions to my test. One rule I usually believe in is that a package shouldn’t depend on any of its child packages. So, for example, ca.intelliware.example should not depend on ca.intelliware.example.impl

Here’s how I can check that, by scanning each package’s efferent couplings.

public void testDependencies() throws Exception {

  ...

  jDepend.analyze();

  Collection packages = jDepend.getPackages();
  for (Iterator i = packages.iterator(); i.hasNext();) {
    JavaPackage javaPackage = (JavaPackage) i.next();
    assertDoesNotDependOnSubPackage(javaPackage);
  }
  assertFalse("contains cycles", jDepend.containsCycles());
}

private void assertDoesNotDependOnSubPackage(JavaPackage javaPackage) {
  Collection dependencies = javaPackage.getEfferents();
  for (Iterator i = dependencies.iterator(); i.hasNext();) {
    JavaPackage dependent = (JavaPackage) i.next();

    if (dependent.getName().startsWith(javaPackage.getName() + ".")) {
      fail("package " + dependent.getName()
        + " should not depend on "
        + javaPackage.getName());
  }
}

I can add other rules, as well. I could check that service-tier code doesn’t depend on web-tier code. I can write a check to ensure that only my DAO packages depend on Hibernate packages, or java.sql.*.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply