diff --git a/doc/architecture_test.md b/doc/architecture_test.md index 8dcc0d1c7e8..aec6f656877 100644 --- a/doc/architecture_test.md +++ b/doc/architecture_test.md @@ -3,13 +3,18 @@ title: Using Spoon for Architecture Enforcement tags: [getting-started] --- -Spoon can be used to check the architecture rules of your application. -For this, the idea is to write a test case that checks the rule. +Spoon can be used to check the architectural rules of your application. +For this, the idea is to write a standard Junit test case that loads the application code and checks the rule. +You only need to depend on spoon at testing time, ie `test` in Maven. + +Example: rules on constructor usage +---------------------------------- + For instance, let's imagine that you want to forbid the usage of `TreeSet`, in your code base, you would simply write a test case as follows: ```java -void noTreeSetInSpoon() throws Exception { - // we don't use TreeSet, because they implicitly depend on Comparable (no static check, only dynamic checks) +@Test +void noTreeSet() throws Exception { SpoonAPI spoon = new Launcher(); spoon.addInputResource("src/main/java/"); spoon.buildModel(); @@ -27,5 +32,26 @@ That's it! Every time you run the tests, incl. on your continuous integration se For instance, you can check that you never return null, or always use an appropriate factory, or that all classes implementing an interface are in the same package. -Note that you only need to depend on spoon at testing time, ie `test` in Maven. +Example: checking naming conventions for test cases +---------------------------- + +A common mistake is to forget to follow a naming convention. For instance, if you use Maven, all test classes must be named `Test*` or `*Test` in order to be run by Maven's standard test plugin `surefire` ([see doc](http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html)). This rule simply reads: + +```java +@Test +public void testGoodTestClassNames() throws Exception { + SpoonAPI spoon = new Launcher(); + spoon.addInputResource("src/test/java/"); + spoon.buildModel(); + + for (CtMethod meth : spoon.getModel().getRootPackage().getElements(new TypeFilter(CtMethod.class) { + @Override + public boolean matches(CtMethod element) { + return super.matches(element) && element.getAnnotation(Test.class) != null; + } + })) { + assertTrue("naming contract violated for "+meth.getParent(CtClass.class).getSimpleName(), meth.getParent(CtClass.class).getSimpleName().startsWith("Test") || meth.getParent(CtClass.class).getSimpleName().endsWith("Test")); + } +} +``` diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java similarity index 71% rename from src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java rename to src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index e27d06e4a70..834350ce804 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcer.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -4,13 +4,13 @@ import spoon.Launcher; import spoon.SpoonAPI; import spoon.reflect.code.CtConstructorCall; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.visitor.filter.AbstractFilter; -import spoon.support.DefaultCoreFactory; -import spoon.support.compiler.SnippetCompilationHelper; +import spoon.reflect.visitor.filter.TypeFilter; import java.util.TreeSet; @@ -18,7 +18,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -public class SpoonArchitectureEnforcer { +public class SpoonArchitectureEnforcerTest { @Test public void statelessFactory() throws Exception { @@ -83,4 +83,26 @@ public void metamodelPackageRule() throws Exception { assertTrue(implType.getReference().isSubtypeOf(interfaceType.getReference())); } } + + + @Test + public void testGoodTestClassNames() throws Exception { + // contract: to be run by Maven surefire, all test classes must be called Test* or *Test + // reference: "By default, the Surefire Plugin will automatically include all test classes with the following wildcard patterns:" + // "**/Test*.java" and "**/*Test.java" + // http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html + SpoonAPI spoon = new Launcher(); + spoon.addInputResource("src/test/java/"); + spoon.buildModel(); + + for (CtMethod meth : spoon.getModel().getRootPackage().getElements(new TypeFilter(CtMethod.class) { + @Override + public boolean matches(CtMethod element) { + return super.matches(element) && element.getAnnotation(Test.class) != null; + } + })) { + assertTrue("naming contract violated for "+meth.getParent(CtClass.class).getSimpleName(), meth.getParent(CtClass.class).getSimpleName().startsWith("Test") || meth.getParent(CtClass.class).getSimpleName().endsWith("Test")); + } + } + }