TNG / ArchUnit

A Java architecture test library, to specify and assert architecture rules in plain Java
http://archunit.org
Apache License 2.0
3.23k stars 298 forks source link

ArchUnit rule for package consistency for test classes #20

Closed JensPiegsa closed 7 years ago

JensPiegsa commented 7 years ago

After some refactorings, I want to ensure that all (.*)Test classes reside in the same package as their corresponding classes-under-test. How to achieve this with ArchUnit?

codecholeric commented 7 years ago

I think there are several ways to test this. I don't know, if you really need the fluent API for this use case, so you could just do this test on a lower level: (for the case of simplicity, I'll assume that the simple name is unique and no class is called sth. like 'FooTestTest' or just 'Test')

@ArchTest
public static void test_classes_should_reside_in_the_correct_package(JavaClasses classes) {
  Map<String, String> simpleTestNameToPackage = new HashMap<>();
  // First collect all tests
  for (JavaClass clazz : classes) {
    if (clazz.getName().endsWith("Test")) {
      simpleTestNameToPackage.put(clazz.getSimpleName(), clazz.getPackage());
    }
  }

  // Then check for all classes that a possibly existing test is in the same package
  for (JavaClass clazz : classes) {
    String possibleTestName = clazz.getSimpleName() + "Test";
    String correspondingTestPackage = simpleTestNameToPackage.get(possibleTestName);
    boolean testExistsButIsInWrongPackage = correspondingTestPackage != null
        && !correspondingTestPackage.equals(clazz.getPackage());

    if (testExistsButIsInWrongPackage) {
      Assert.fail(String.format("Test class %s.%s is in the wrong package",
          correspondingTestPackage, possibleTestName));
    }
  }
}

You'll probably have to refine this a little more, for example you could consider 'JavaClass.getSource()' to find out if the class you're checking is in main or test.

If you want to use the fluent API at all costs :wink: , you could initialize a custom condition by overriding init(..), and write it in the following way (the logic would be something similar to the low level case, I think)

classes().should(new ArchCondition<JavaClass>("have their test in the same package") {
  @Override
  public void init(Iterable<JavaClass> allClasses) {
    // find all tests to begin with, and store them
  }

  @Override
  public void check(JavaClass item, ConditionEvents events) {
    // check every class with respect to the stored classes from init()
  }
});

(of course it would look neater, if the condition was declared in a separate method)

codecholeric commented 7 years ago

Can you give me an update, if you were able to figure out your issue? (Because then I could close this issue :wink: )

JensPiegsa commented 7 years ago

Many thanks for coming back to this. Your provided solution is working for us!