What I would like to achieve is to write an ArchUnit rule to make sure our packages stay small enough.
For instance, suppose this:
natural
├─ goodpackage
│ ├─ apple
│ ├─ banana
│ ├─ mango
├─ badpackage
│ ├─ file1
│ ├─ file2
│ ├─ ...
│ ├─ file10
For this example, let's say more than 9 files is too much, while up to 8 files is small enough.
This is also just for leaf packages, i.e. the most bottom level packages (if there are files in this package, but also sub packages, the test should aim to the sub packages)
I tried using the class com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
and was hoping to get a way to iterate inside a package. but no luck.
I also searched if we can directly get the packages from ArchUnit, something like:
@Test
void leafPackagesShouldNotHaveMoreThan9Files() { }
packages().that().areInSubPackages().shouldNot().have(moreThan9Files()).check(importedClasses);
}
But also not possible.
How to check if there are less than N files in a leaf package with ArchUnit?
You can probably use something like this:
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaPackage;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.AbstractClassesTransformer;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ClassesTransformer;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.all;
import static java.util.stream.Collectors.toSet;
...
@ArchTest
ArchRule leaf_packages_should_have_max_number_of_classes = all(packages())
.that(are(describe("leaves", p -> p.getSubpackages().isEmpty())))
.should(haveMaxNumberOfClasses(9));
static ClassesTransformer<JavaPackage> packages() {
return new AbstractClassesTransformer<JavaPackage>("packages") {
@Override
public Iterable<JavaPackage> doTransform(JavaClasses javaClasses) {
return javaClasses.stream().map(JavaClass::getPackage).collect(toSet());
}
};
}
static ArchCondition<JavaPackage> haveMaxNumberOfClasses(int maxNumberOfClasses) {
return new ArchCondition<JavaPackage>("have at most " + maxNumberOfClasses + " classes") {
@Override
public void check(JavaPackage p, ConditionEvents events) {
long numberOfClasses = p.getClasses().stream().count();
boolean satisfied = numberOfClasses <= maxNumberOfClasses;
String message = p.getDescription() + " contains " + numberOfClasses + " classes";
events.add(new SimpleConditionEvent(p, satisfied, message));
}
};
}