JDepend is probably the best known of the package dependency tools. It has certainly been around for quite a while.
JDepend allows you to analyse java package metrics, dependencies, and cycles and allows you to visualise the analysis in a graphical UI, textual UI, and XML UI. I won’t go into detail on how to do this since it would largely require me to regurgitate what is already documented at the JDepend web site. My real interest is how can I integrate this into my build and have my build break when certain dependencies break.
JDepend allows you to right JUnit tests that codify the dependency constraints you wish to enforce. If any of these dependency constraints are broken, the unit test will fail. An example of such a unit test can be seen below.
package com.gigantiq;
import jdepend.framework.DependencyConstraint;
import jdepend.framework.JDepend;
import jdepend.framework.JavaPackage;
import jdepend.framework.PackageFilter;
import static junit.framework.Assert.assertEquals;
import org.junit.Test;
import java.io.IOException;
/**
* Copyright Gigantiq Pty Ltd
*/
public class EnforcePackageDependenciesTest
{
@Test
public void testMatch() throws IOException
{
PackageFilter filter = new PackageFilter();
filter.addPackage(”java.*”);
filter.addPackage(”javax.*”);
JDepend jdepend = new JDepend(filter);
jdepend.addDirectory(”/Path/To/Classes/”);
DependencyConstraint constraint = new DependencyConstraint();
JavaPackage model = constraint.addPackage(”com.gigantiq.bedrock.model”);
JavaPackage ui = constraint.addPackage(”com.gigantiq.bedrock.ui”);
JavaPackage util = constraint.addPackage(”com.gigantiq.bedrock.util”);
ui.dependsUpon(model);
ui.dependsUpon(util);
model.dependsUpon(util);
jdepend.analyze();
assertEquals(”Dependency mismatch”, true, jdepend.dependencyMatch(constraint));
}
}
To implement such a unit test, the first step is to create an instance of JDepend. In our exmpale we inject the JDepend instance with a package filter to ensure the java.* and javax.* packages are ignored during the analysis.
PackageFilter filter = new PackageFilter();
filter.addPackage("java.*");
filter.addPackage("javax.*");
JDepend jdepend = new JDepend(filter);
Our dependencies are then coded into the unit test by setting up a DependencyConstraint defining the allowed dependencies between packages.
DependencyConstraint constraint = new DependencyConstraint();
JavaPackage model = constraint.addPackage("com.gigantiq.bedrock.model");
JavaPackage ui = constraint.addPackage("com.gigantiq.bedrock.ui");
JavaPackage util = constraint.addPackage("com.gigantiq.bedrock.util");
ui.dependsUpon(model);
ui.dependsUpon(util);
Once our dependencies are coded up into such a unit test, it is a simple manner to include the execution of this test into our build since it can be treated like any other JUnit test in our system. Adding any new code that violates the defined constraints will result in a failing unit test.
There a number of issues with this approach of enforcing package dependencies that I really do not like.
- Enforcing the package dependencies is important enough that it should be treated as a seperate target with in the build. The danger of treating it as “just” another unit test is that we lose the sense of importance it should retain. Defining a seperate target in the build that just runs the JDepend unit tests would go a long way towards addressing this issue.
- One of my driving forces behind enforcing package dependency, is that I can better understand the applications structure and thus be able to improve my ability to understand the impact that a code change is going to have on the rest of the system. I do not find JDepend’s approach of defining dependencies particularly easy to ready. A JDepend test for a complex applicaion with a lot of dependencies defined can become very difficult to read.
- Error reporting is really poor. If my unit test fails, I get a message printed to the console that looks something like this…
junit.framework.AssertionFailedError: Dependency mismatch expected:<true> but was:<false>. What dependency has been breached? I could structure my test in such a way that makes determining the cause of such errors much easier, but I would prefer a tool that just offered better reporting with out much effort.
So while JDepend will give me an approach for enforcing package dependencies with in my application, it is far from being ideal.