Enforcing architecture rules with a unit test…
Posted February 24th, 2010 by Julien… is neat!
At the beginning of a project, everybody in the team agrees to make things properly, identify layers, isolate responsibilities, code with defined formatting rules, etc. But after a few weeks, rules tend to be bent and laxism is all over the place. Formatting rules can be checked at code-time using Checkstyle Eclipse plugin, test coverage can be monitored using Maven Cobertura plugin (build-time) or EclEmma Eclispe plugin (test-time), but what can one use to enforce a certain set of architecture rules?
You use a unit test that triggers a Checkstyle “importControl” module of course! Okay, you will use the Checkstyle API, so you need the checkstyle JAR on your classpath. The unit test will initialize an audit context, referencing a configuration file that has to be on the classpath too :
public class CheckstyleIntegrationTest { class Listener implements AuditListener { public List<AuditEvent> errorList = new ArrayList<AuditEvent>(); public void addError(AuditEvent arg0) { errorList.add(arg0); } public void addException(AuditEvent arg0, Throwable arg1) { throw new RuntimeException("Error while executing checksyle", arg1); } ... } @SuppressWarnings("unchecked") private List<File> getList(String... directories) { List<File> l = new ArrayList<File>(); for (String s : directories) { l.addAll(FileUtils.listFiles(new File(s), new String[] { "java" }, true)); } return l; } @Test public void check() throws Exception { InputStream is = getClass().getResourceAsStream("/checkstyle.xml"); PropertyResolver propertyResolver = new PropertyResolver() { public String resolve(String arg0) throws CheckstyleException { return null; } }; Configuration configuration = ConfigurationLoader.loadConfiguration(is, propertyResolver, false); Checker checker = new Checker(); checker.setModuleClassLoader(getClass().getClassLoader()); checker.configure(configuration); Listener listener = new Listener(); checker.addListener(listener); checker.process(getList("src/main/java", "src/mock/java")); for (AuditEvent auditEvent : listener.errorList) { System.err.println("Problem in file " + auditEvent.getFileName() + " : " + auditEvent.getMessage()); } Assert.assertEquals(0, listener.errorList.size()); } }
Then in your checkstyle.xml configuration file :
<?xml version="1.0"?> <!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.2//EN" "http://www.puppycrawl.com/dtds/configuration_1_2.dtd"> <module name="Checker"> <module name="TreeWalker"> <module name="ImportControl"> <property name="file" value="src/test/resources/import-control.xml" /> </module> </module> </module>
And the only thing left is to specify your rules, for example :
<?xml version="1.0"?> <!DOCTYPE import-control PUBLIC "-//Puppy Crawl//DTD Import Control 1.0//EN" "http://www.puppycrawl.com/dtds/import_control_1_0.dtd"> <import-control pkg="com.mycompany"> <subpackage name="services"> <!-- common packages --> <allow pkg="java" /> <allow pkg="javax" /> <allow pkg="org.springframework" /> <allow pkg="org.apache.commons" /> <allow pkg="org.apache.log4j" /> <!-- application layers --> <allow pkg="com.mycompany.dao" /> <allow pkg="com.mycompany.model" /> etc ....