Back to Reference
ReferenceConfiguration

Configuration

visdom-testing.yaml reference, CI integration, and tool configuration.

Configuration file

All Visdom Testing settings live in a single visdom-testing.yaml file at the project root. Each layer can be enabled or disabled independently, and thresholds are configurable per layer.

# visdom-testing.yaml
version: "1.0"

layers:
  architecture:
    enabled: true
    fail-on-violation: true
    baseline: archunit_store/   # FreezingArchRule baseline directory

  property-based:
    enabled: true
    default-tries: 1000         # tries per property (override per-test)
    seed: fixed                 # "fixed" for reproducibility, "random" for exploration
    timeout-per-property: 10s

  mutation:
    enabled: true
    target-classes:
      - "com.example.pricing.*"
      - "com.example.billing.*"
    excluded-classes:
      - "com.example.*.config.*"
      - "com.example.*.dto.*"
    mutators:
      - DEFAULTS                # PIT default mutator group
      - EXTENDED                # additional mutators for thorough analysis
    threshold:
      minimum-score: 60         # percentage, PR gate
      warn-score: 75            # percentage, warning level

  contracts:
    enabled: true
    broker-url: "https://pact-broker.example.com"
    publish-results: true
    provider-version-from: git-commit
    consumer-version-selectors:
      - tag: main
      - deployed: true

quality-gates:
  mutation-score: 60            # minimum mutation score for changed files
  architecture-compliance: true # zero ArchUnit violations
  contract-verification: true   # all contracts must pass
  max-flakiness: 2              # percentage, per-run flakiness budget

ci:
  pr-scope: changed-files       # "changed-files" or "changed-modules"
  nightly-scope: full-modules
  parallel: true
  timeout: 30m

Layer 0: ArchUnit

Test placement

Place your architecture tests in a dedicated test class, typically at src/test/java/com/example/architecture/ArchitectureTest.java. Use the @AnalyzeClasses annotation to scope the analysis.

@AnalyzeClasses(packages = "com.example", importOptions = {
    ImportOption.DoNotIncludeTests.class
})
class ArchitectureTest {

    @ArchTest
    static final ArchRule controllers_should_not_access_repositories =
        noClasses()
            .that().resideInAPackage("..controller..")
            .should().accessClassesThat()
            .resideInAPackage("..repository..");

    @ArchTest
    static final ArchRule no_field_injection =
        noFields()
            .should().beAnnotatedWith("org.springframework.beans.factory.annotation.Autowired");

    @ArchTest
    static final ArchRule no_generic_exceptions =
        noClasses()
            .should().callMethodWhere(
                JavaCall.Predicates.target(HasName.Predicates.name("throw"))
            );
}

FreezingArchRule for baselines

When adopting ArchUnit on a legacy codebase, use FreezingArchRule to capture existing violations as a baseline. New violations fail the build; existing ones are tracked and reduced over time.

@ArchTest
static final ArchRule no_rest_template = FreezingArchRule.freeze(
    noClasses()
        .should().accessClassesThat()
        .haveFullyQualifiedName("org.springframework.web.client.RestTemplate")
);

The baseline is stored in archunit_store/ by default. Commit this directory to version control so the baseline is shared across the team.

Pre-push hook setup

# .git/hooks/pre-push
#!/bin/sh
echo "Running ArchUnit tests..."
mvn test -pl :architecture-tests -Dtest=ArchitectureTest -q
if [ $? -ne 0 ]; then
    echo "ArchUnit violations detected. Push rejected."
    exit 1
fi

Layer 1: Property-Based Testing (jqwik)

jqwik configuration

jqwik runs as a JUnit 5 test engine. Add the dependency and configure via jqwik.properties or annotations.

# src/test/resources/jqwik.properties
jqwik.tries.default=1000
jqwik.maxDiscardRatio=5
jqwik.reporting.onlyFailures=true
jqwik.database=.jqwik-database

Tries per property

Context Tries Use case
Development 100 Fast feedback during TDD loop
CI (per PR) 1,000 Standard verification on each push
Nightly / release 10,000 Thorough exploration for rare edge cases

Seed management

jqwik records failing seeds in .jqwik-database. When a property fails, jqwik replays the failing seed on subsequent runs to ensure the bug stays caught until fixed. Commit .jqwik-database or set a fixed seed in CI for reproducibility.

Layer 2: Mutation Testing (PIT)

PIT configuration

<!-- pom.xml -->
<plugin>
  <groupId>org.pitest</groupId>
  <artifactId>pitest-maven</artifactId>
  <version>1.17.1</version>
  <configuration>
    <targetClasses>
      <param>com.example.pricing.*</param>
      <param>com.example.billing.*</param>
    </targetClasses>
    <excludedClasses>
      <param>com.example.*.config.*</param>
      <param>com.example.*.dto.*</param>
    </excludedClasses>
    <mutators>
      <mutator>DEFAULTS</mutator>
    </mutators>
    <timestampedReports>false</timestampedReports>
    <outputFormats>
      <param>HTML</param>
      <param>XML</param>
    </outputFormats>
    <mutationThreshold>60</mutationThreshold>
  </configuration>
</plugin>

Target classes

Focus mutation testing on business logic, not infrastructure. Exclude DTOs, configuration classes, and generated code. Mutating these adds CI time without finding real bugs.

Mutator selection

Mutator group What it does When to use
DEFAULTS Conditionals, math, return values Always — baseline mutators
EXTENDED Adds constructor calls, inline constants, remove conditionals Nightly runs for deeper analysis
ALL Every available mutator Release gate / audit only

Thresholds

Set mutationThreshold to your minimum acceptable score. Start at 60% and raise it as your test suite improves. A score below 60% typically indicates significant gaps in test effectiveness.

Layer 3: Contract Testing (Pact)

Pact Broker configuration

# visdom-testing.yaml (contracts section)
contracts:
  broker-url: "https://pact-broker.example.com"
  publish-results: true
  provider-version-from: git-commit
  consumer-version-selectors:
    - tag: main           # verify against main branch consumers
    - deployed: true      # verify against currently deployed consumers

Provider verification

@Provider("pricing-service")
@PactBroker(
    url = "\$\{pact.broker.url\}",
    consumerVersionSelectors = {
        @ConsumerVersionSelector(tag = "main"),
        @ConsumerVersionSelector(deployed = true)
    }
)
class PricingProviderTest {
    // Provider state setup and verification
}

Contract versioning

Use git commit SHA as the provider version. This ensures each build is uniquely identified and the Pact Broker can track which versions are compatible. Tag versions with the branch name for consumer version selection.

CI Integration

GitHub Actions workflow

# .github/workflows/visdom-testing.yml
name: Visdom Testing
on:
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'  # nightly at 2 AM

jobs:
  architecture-and-properties:
    name: L0 Architecture + L1 PBT
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Run ArchUnit + PBT tests
        run: mvn test -pl :architecture-tests,:property-tests -q

  mutation:
    name: L2 Mutation (changed files)
    needs: architecture-and-properties
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Run PIT on changed modules
        run: |
          CHANGED=\$(git diff --name-only origin/main...HEAD -- '*.java' | \\
            sed 's|src/main/java/||;s|/[^/]*\$||;s|/|.|g' | sort -u)
          mvn org.pitest:pitest-maven:mutationCoverage \\
            -DtargetClasses="\$\{CHANGED\}" \\
            -DmutationThreshold=60

  contracts:
    name: L3 Contract Verification
    needs: architecture-and-properties
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Verify provider contracts
        run: mvn test -pl :contract-tests -q
        env:
          PACT_BROKER_URL: \$\{\{ secrets.PACT_BROKER_URL \}\}

  mutation-nightly:
    name: L2 Mutation (full modules)
    runs-on: ubuntu-latest
    if: github.event_name == 'schedule'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Run full mutation analysis
        run: mvn org.pitest:pitest-maven:mutationCoverage

GitLab CI example

# .gitlab-ci.yml
stages:
  - fast-checks
  - pr-checks
  - nightly

archunit-pbt:
  stage: fast-checks
  script:
    - mvn test -pl :architecture-tests,:property-tests -q
  rules:
    - if: \$CI_PIPELINE_SOURCE == "merge_request_event"
    - if: \$CI_PIPELINE_SOURCE == "schedule"

mutation-pr:
  stage: pr-checks
  script:
    - >
      CHANGED=\$(git diff --name-only \$CI_MERGE_REQUEST_DIFF_BASE_SHA...HEAD -- '*.java' |
      sed 's|src/main/java/||;s|/[^/]*\$||;s|/|.|g' | sort -u)
    - mvn org.pitest:pitest-maven:mutationCoverage
      -DtargetClasses="\$\{CHANGED\}"
      -DmutationThreshold=60
  rules:
    - if: \$CI_PIPELINE_SOURCE == "merge_request_event"

contracts:
  stage: pr-checks
  script:
    - mvn test -pl :contract-tests -q
  rules:
    - if: \$CI_PIPELINE_SOURCE == "merge_request_event"
    - if: \$CI_PIPELINE_SOURCE == "schedule"

mutation-full:
  stage: nightly
  script:
    - mvn org.pitest:pitest-maven:mutationCoverage
  rules:
    - if: \$CI_PIPELINE_SOURCE == "schedule"

Quality gates

Quality gates are configured in the quality-gates section of visdom-testing.yaml and enforced in CI.

Gate Condition Default
Mutation score Changed files must meet minimum mutation score 60%
Architecture compliance Zero ArchUnit violations Enabled
Contract verification All consumer-driven contracts pass Enabled
Flakiness budget Per-run flakiness below threshold 2%

Start with warnings, then enforce

When first deploying, set quality gates to warn-only mode. Teams see the metrics without being blocked. After 2-4 weeks, once baselines are established and teams understand the metrics, switch to enforcement mode.