Architecture testing framework for Apache Flink test code using ArchUnit
npx @tessl/cli install tessl/maven-org-apache-flink--flink-architecture-tests-test@2.1.0Flink Architecture Tests - Test provides architectural testing rules specifically designed for Apache Flink's test code infrastructure. Built on ArchUnit, it enables enforcement of architectural rules and coding standards within Flink's testing ecosystem, helping maintain code quality and architectural integrity across Flink's extensive test suite.
pom.xml<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-architecture-tests-test</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>import org.apache.flink.architecture.TestCodeArchitectureTestBase;
import org.apache.flink.architecture.rules.ITCaseRules;
import org.apache.flink.architecture.rules.BanJunit4Rules;
import org.apache.flink.architecture.common.Conditions;
import org.apache.flink.architecture.common.JavaFieldPredicates;
import org.apache.flink.architecture.common.Predicates;
import org.apache.flink.architecture.common.ImportOptions;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchTests;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.core.importer.ImportOption;package org.apache.flink.architecture;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchTests;
import com.tngtech.archunit.core.importer.ImportOption;
import org.apache.flink.architecture.TestCodeArchitectureTestBase;
import org.apache.flink.architecture.common.ImportOptions;
@AnalyzeClasses(
packages = {"org.apache.flink.your.module"},
importOptions = {
ImportOption.OnlyIncludeTests.class,
ImportOptions.ExcludeScalaImportOption.class,
ImportOptions.ExcludeShadedImportOption.class
}
)
public class TestCodeArchitectureTest {
// Include common architectural tests from the base
@ArchTest
public static final ArchTests COMMON_TESTS =
ArchTests.in(TestCodeArchitectureTestBase.class);
}Create archunit.properties in src/test/resources/:
# Controls if the violation store is writable (enables updating existing violations)
freeze.store.default.allowStoreUpdate=true
# Path to store violations relative to project root
freeze.store.default.path=archunit-violations
# Allow creation of new violation stores (uncomment when setting up new tests)
# freeze.store.default.allowStoreCreation=true
# Re-freeze all violations to current state (uncomment for rule changes)
# freeze.refreeze=trueCreate log4j2-test.properties in src/test/resources/ for logging configuration during tests:
# Set root logger to OFF to prevent flooding build logs during architectural testing
rootLogger.level=OFFFlink Architecture Tests - Test is built around several key components:
TestCodeArchitectureTestBase provides a central setup for common architectural testsITCaseRules, BanJunit4Rules) that group related architectural constraintsConditions, JavaFieldPredicates, Predicates) for building custom rulesImportOptions) to exclude Scala and shaded classes from analysisCentral setup class that provides common architectural tests for all Flink submodules.
public class TestCodeArchitectureTestBase {
@ArchTest
public static final ArchTests ITCASE = ArchTests.in(ITCaseRules.class);
}Use this class to include standard architectural tests in your submodule:
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchTests;
import org.apache.flink.architecture.TestCodeArchitectureTestBase;
public class TestCodeArchitectureTest {
@ArchTest
public static final ArchTests COMMON_TESTS =
ArchTests.in(TestCodeArchitectureTestBase.class);
}Enforces naming conventions and MiniCluster resource usage for integration tests.
public class ITCaseRules {
@ArchTest
public static final ArchRule INTEGRATION_TEST_ENDING_WITH_ITCASE;
@ArchTest
public static final ArchRule ITCASE_USE_MINICLUSTER;
}INTEGRATION_TEST_ENDING_WITH_ITCASE: Ensures that classes inheriting from AbstractTestBase have names ending with "ITCase".
ITCASE_USE_MINICLUSTER: Validates that ITCase tests use appropriate MiniCluster resources. Has different requirements for runtime vs non-runtime packages:
For runtime packages (org.apache.flink.runtime.*):
InternalMiniClusterExtension for JUnit 5MiniClusterWithClientResource for JUnit 4For non-runtime packages:
MiniClusterExtension for JUnit 5MiniClusterWithClientResource for JUnit 4JUnit 5 examples:
// For runtime packages
@RegisterExtension
public static final InternalMiniClusterExtension MINI_CLUSTER_RESOURCE =
new InternalMiniClusterExtension(
new MiniClusterResourceConfiguration.Builder()
.setConfiguration(getFlinkConfiguration())
.build());
// For non-runtime packages
@RegisterExtension
public static final MiniClusterExtension MINI_CLUSTER_RESOURCE =
new MiniClusterExtension(
new MiniClusterResourceConfiguration.Builder()
.setConfiguration(getFlinkConfiguration())
.build());
// Alternative annotation-based approach
@ExtendWith(MiniClusterExtension.class)
public class MyITCase {
// test methods
}JUnit 4 examples (legacy):
@Rule
public final MiniClusterWithClientResource miniClusterResource =
new MiniClusterWithClientResource(
new MiniClusterResourceConfiguration.Builder()
.setNumberTaskManagers(1)
.setNumberSlotsPerTaskManager(PARALLELISM)
.build());
@ClassRule
public static final MiniClusterWithClientResource miniClusterResource =
new MiniClusterWithClientResource(
new MiniClusterResourceConfiguration.Builder()
.setNumberTaskManagers(1)
.setNumberSlotsPerTaskManager(PARALLELISM)
.build());Enforces migration from JUnit 4 to JUnit 5 by preventing new JUnit 4 dependencies.
public class BanJunit4Rules {
@ArchTest
public static final ArchRule NO_NEW_ADDED_JUNIT4_TEST_RULE;
}NO_NEW_ADDED_JUNIT4_TEST_RULE: Prevents classes in org.apache.flink.. packages from depending on JUnit 4 classes (junit, org.junit packages). Uses freezing mechanism to track existing violations while preventing new ones.
Core utility classes from flink-architecture-tests-base for building custom architectural rules.
Generic conditions for testing architectural properties.
public class Conditions {
/**
* Creates condition to check fulfillment of predicates
*/
public static <T> ArchCondition<T> fulfill(DescribedPredicate<T> predicate);
/**
* Tests leaf types of methods (return types, parameter types, exception types)
*/
public static ArchCondition<JavaMethod> haveLeafTypes(DescribedPredicate<JavaClass> predicate);
/**
* Tests leaf return types of methods
*/
public static ArchCondition<JavaMethod> haveLeafReturnTypes();
/**
* Tests leaf argument types of methods
*/
public static ArchCondition<JavaMethod> haveLeafArgumentTypes();
/**
* Tests leaf exception types of methods
*/
public static ArchCondition<JavaMethod> haveLeafExceptionTypes();
}Predicates for testing Java field properties.
public class JavaFieldPredicates {
// Field modifier predicates
public static DescribedPredicate<JavaField> isPublic();
public static DescribedPredicate<JavaField> isStatic();
public static DescribedPredicate<JavaField> isNotStatic();
public static DescribedPredicate<JavaField> isFinal();
// Type matching predicates
public static DescribedPredicate<JavaField> ofType(Class<?> type);
public static DescribedPredicate<JavaField> ofType(String typeName);
public static DescribedPredicate<JavaField> isAssignableTo(Class<?> type);
// Annotation predicates
public static DescribedPredicate<JavaField> annotatedWith(Class<? extends Annotation> annotationType);
public static DescribedPredicate<JavaField> annotatedWith(String annotationTypeName);
}Complex field predicates combining multiple constraints.
public class Predicates {
// Combined field predicates
public static DescribedPredicate<JavaField> arePublicStaticOfType(Class<?> type);
public static DescribedPredicate<JavaField> arePublicFinalOfType(Class<?> type);
public static DescribedPredicate<JavaField> arePublicStaticFinalAssignableTo(Class<?> type);
// Annotation combination predicates
public static DescribedPredicate<JavaField> arePublicFinalOfTypeWithAnnotation(
Class<?> type, Class<? extends Annotation> annotationType);
public static DescribedPredicate<JavaField> arePublicStaticFinalOfTypeWithAnnotation(
Class<?> type, Class<? extends Annotation> annotationType);
public static DescribedPredicate<JavaField> areStaticFinalOfTypeWithAnnotation(
Class<?> type, Class<? extends Annotation> annotationType);
// Utility methods
public static String getClassSimpleNameFromFqName(String fullyQualifiedName);
public static <T> DescribedPredicate<T> exactlyOneOf(DescribedPredicate<? super T>... predicates);
}Custom import filtering options for ArchUnit class analysis.
public class ImportOptions {
/**
* Excludes Scala classes from analysis
*/
public static class ExcludeScalaImportOption implements ImportOption;
/**
* Excludes shaded/relocated classes from analysis
*/
public static class ExcludeShadedImportOption implements ImportOption;
/**
* Imports only main classes (excludes test classes)
*/
public static class MavenMainClassesOnly implements ImportOption;
}When modifying existing rules, regenerate violation stores:
# Remove existing violation stores
rm -rf `find . -type d -name archunit-violations`
# Regenerate stores with current state
mvn test -Dtest="*TestCodeArchitectureTest*" \
-DfailIfNoTests=false \
-Darchunit.freeze.refreeze=true \
-Darchunit.freeze.store.default.allowStoreCreation=true \
-Dfast// From ArchUnit framework
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchTests;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.importer.ImportOption;
// Annotation for marking architectural test fields
@interface ArchTest {
}
// Container for multiple architectural tests
class ArchTests {
static ArchTests in(Class<?> testClass);
}
// Individual architectural rule
interface ArchRule {
ArchRule allowEmptyShould(boolean allow);
ArchRule as(String description);
}
// Annotation for specifying which classes to analyze
@interface AnalyzeClasses {
String[] packages();
Class<? extends ImportOption>[] importOptions() default {};
}
// Base types for predicates and conditions
interface DescribedPredicate<T> {
boolean test(T input);
DescribedPredicate<T> and(DescribedPredicate<? super T> other);
DescribedPredicate<T> or(DescribedPredicate<? super T> other);
}
interface ArchCondition<T> {
ArchCondition<T> and(ArchCondition<? super T> condition);
ArchCondition<T> or(ArchCondition<? super T> condition);
ArchCondition<T> as(String description);
}
// Import filtering interface
interface ImportOption {
boolean includes(Location location);
}The package uses ArchUnit's configuration system through archunit.properties:
freeze.store.default.allowStoreUpdate: Controls if violation store is writablefreeze.store.default.allowStoreCreation: Enables creation of new violation storesfreeze.refreeze: Records current state of violationsfreeze.store.default.path: Path to store violations (default: "archunit-violations")When architectural tests fail, ArchUnit provides detailed violation reports including:
Common failure scenarios:
AbstractTestBase without "ITCase" suffixMiniClusterExtension instead of InternalMiniClusterExtensionarchunit.properties setupFreezingArchRule.freeze()Violation Store Issues:
freeze.store.default.allowStoreCreation=true in archunit.propertiesfreeze.refreeze=true optionarchunit-violations directory and should be committed to version controlRule Freezing Requirements:
FreezingArchRule.freeze() wrapper.as(String) to reduce maintenance overheadPackage-Specific Rule Failures:
InternalMiniClusterExtension for JUnit 5MiniClusterExtension for JUnit 5MiniClusterWithClientResource for JUnit 4 compatibility