Files
werkcrappit/docs/pitest-mutation-crap-coding-agent-auftrag.md
mhoennigandClaude Opus 5.5 6e3ec16f21 Initial skeleton: PIT plugin and Gradle plugin modules
werkcrappit computes an experimental Mutation-CRAP score per JVM method
from the JaCoCo complexity of the original class files and the PIT
mutation score. The specification is
docs/pitest-mutation-crap-coding-agent-auftrag.md.

This commit holds only the skeleton: Gradle 9.8.0 wrapper, Java 21
toolchain with JUnit Jupiter and AssertJ, the modules werkcrappit-pit
and werkcrappit-gradle-plugin (with a no-op plugin class), README,
AGENTS.md and LICENSE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:59:49 +02:00

60 KiB
Raw Permalink Blame History

Implementierungsauftrag: Mutation-CRAP als eigenständiges PIT-Plugin

Adressat: Codex oder ein anderer Coding Agent
Arbeitsname: pitest-mutation-crap
Stand: 1. Oktober 2026
Ziel: Ein funktionierendes, getestetes Plugin implementieren, nicht nur einen Entwurf erstellen.

1. Auftrag und Motivation

Implementiere in einem eigenständigen Git-Repository ein Reporting-Plugin für PIT / PITest, das für JVM-Methoden einen mutationsbasierten CRAP-Wert berechnet. Die Integration und die unterstützten Anwender-Workflows erfolgen ausschließlich über Gradle. Java hat höchste Priorität; Kotlin/JVM ist ein nachgeordnetes Kompatibilitätsziel.

Gemeint ist PIT für Mutation Testing auf der JVM, nicht das Python-Testframework pytest.

Die Motivation: Wir möchten Methoden identifizieren, bei denen hohe strukturelle Komplexität mit einer geringen Fähigkeit der Tests zusammenkommt, die von PIT erzeugten Verhaltensänderungen zu erkennen. Dafür kombinieren wir Mutationsergebnisse aus PIT mit der zyklomatischen Komplexität derselben Methode aus JaCoCo Core.

Die vorgeschlagene Mutation-CRAP-Formel ist für dieses Projekt eine experimentelle, ausdrücklich definierte Metrik. Behaupte weder, sie sei ein offizieller CRAP-Nachfolger, noch, ihre Überlegenheit oder ein bestimmter Grenzwert seien empirisch bewiesen. Die erste Version soll die praktische Erprobung ermöglichen. Eine spätere Evaluation darf die Definition ändern, aber nur mit neuer Metrikversion und dokumentierter Entscheidung.

Arbeite in überprüfbaren vertikalen Schritten: zuerst ein echter Gradle/PIT-Durchstich, anschließend Berechnung, Reports und optional einschaltbare Build-Prüfung. Erzeuge nicht zuerst eine umfangreiche Architektur ohne funktionierenden Integrationsnachweis.

2. Verbindlicher Rahmen und Abgrenzung

2.1 Bereits festgelegte Anforderungen

  • Eigenständiges Repository und unabhängig versioniertes PIT-Plugin; kein Fork und keine Änderung am PIT-Core als Voraussetzung.
  • Gradle als einziges unterstütztes Buildsystem. Kein Maven-Plugin, kein Maven-Beispielprojekt und keine Maven-Testmatrix erforderlich.
  • JVM als Zielplattform, Java als verbindliches erstes Sprachziel. Kotlin/JVM nach Möglichkeit unterstützen; andere JVM-Sprachen nicht pauschal als unterstützt bewerben.
  • Mutationsergebnisse direkt aus der PIT-Listener-/Plugin-Schnittstelle beziehen, nicht aus einem nachgeschalteten Import von mutations.xml.
  • Komplexität mit JaCoCo Core aus den originalen, unmutierten Class-Dateien ermitteln.
  • Beide Datenquellen über Klassenname, JVM-Methodenname und JVM-Methodendeskriptor verbinden.
  • Kein eigener Coverage-Testlauf, kein JaCoCo-Agent und keine JaCoCo-.exec-Datei als Voraussetzung für diese Metrik.
  • Kein zusätzlicher PIT-Lauf, den die Metrikberechnung auslöst. PITs eigene Testausführung und vorbereitende Coverage-Ermittlung bleiben davon unberührt.
  • Maschinenlesbarer Report und ein in CI gut lesbarer Report; zusätzlich eine optional einschaltbare Grenzwertprüfung.
  • Gradle-Multi-Project-Builds berücksichtigen, zunächst mit getrennten Auswertungen pro Teilprojekt beziehungsweise PIT-Analyse.
  • Explizite Ausschlüsse ermöglichen. Statusbehandlung, fehlende Daten und Aussagegrenzen müssen nachvollziehbar sein.

Ein PIT-Plugin ist hier ein von PIT geladenes JAR. Das vorhandene Gradle-PIT-Plugin kann solche Abhängigkeiten laden; ein eigenes Gradle-Plugin ist deshalb nicht die Voraussetzung für die Metrikimplementierung. [S1][S9]

2.2 Nicht Bestandteil der ersten Version

Keine eigene Mutation Engine, keine neuen Mutatoren, keine automatische Erkennung äquivalenter Mutanten und keine eigene Implementierung zyklomatischer Komplexität. Ebenso wenig: eigenständige CLI, zentraler Server, Webdienst, SonarQube-Integration, IDE-Plugin, Git-Hosting-Anbindung oder automatische PR-Kommentare.

Ein projektübergreifender Gesamtreport, Baselines und ein Gate ausschließlich für geänderte Methoden sind mögliche spätere Erweiterungen. Implementiere sie nicht vor dem funktionierenden Java-MVP. Addiere nicht einfach Methodenwerte zu einer angeblich etablierten „Projekt-CRAP-Metrik“.

Kein kostenpflichtiges Add-on darf für den Java-MVP erforderlich sein. Insbesondere soll das optionale Kotlin-Ziel den Java-MVP weder blockieren noch stillschweigend eine zusätzliche Lizenz voraussetzen.

Die Veröffentlichung eines JARs in einem Maven-kompatiblen Artefakt-Repository ist von Maven als Buildsystem zu unterscheiden und ist zulässig. Eine Veröffentlichung auf externen Diensten oder das Anlegen eines Remote-Repositories gehört ohne gesonderten Auftrag nicht zur Ausführung dieses Arbeitsauftrags.

2.3 Konkrete Arbeitsentscheidungen dieser Spezifikation

Die folgenden Details sind bewusst gewählte Startentscheidungen, nicht bereits vorher getroffene Anwenderentscheidungen:

  • Name des PIT-Ausgabeformats: MUTATION_CRAP.
  • Metrikkennung: mutation-crap; initiale Metrikversion: 1.
  • Statuspolicy: strict-v1, definiert in Abschnitt 5.
  • Primäre Ausgabe: JSON; lesbare Ausgabe: Markdown plus kurze Konsolenzusammenfassung.
  • Standardmodus: Report erstellen, aber keinen Build allein wegen eines hohen Scores abbrechen.
  • Ein aktiviertes Score-Gate verlangt einen ausdrücklich konfigurierten Grenzwert. 30 ist nur ein Testbeispiel, keine Empfehlung und kein impliziter Standardwert.
  • Neue Produktionslogik vorzugsweise in Java, Buildskripte vorzugsweise als Gradle Kotlin DSL. Java-Code mit var beziehungsweise final var, wo dies mit der gewählten Java-Version und der Lesbarkeit vereinbar ist.
  • Tests mit JUnit Jupiter und AssertJ; Mockito nur dort, wo ein Testdouble sinnvoller ist als ein kleines reales Fixture.

Dokumentiere notwendige Änderungen dieser Arbeitsentscheidungen. Ändere nicht unbemerkt die Metrikformel, ihre Statuspolicy oder die verbindlichen Rahmenbedingungen.

3. Zuerst die technische Machbarkeit nachweisen

3.1 Was bereits belegt ist — und was nicht

PIT bietet MutationResultListenerFactory als Erweiterungspunkt für Ergebnis-Plugins. Listener werden als Service registriert; ein Ausgabeformat kann über outputFormats aktiviert werden. Die dokumentierten Erweiterungsschnittstellen sind allerdings nicht als über alle Versionen unveränderlich zugesagt. [S1][S2]

Der untersuchte Listener besitzt runStart(), handleMutationResult(ClassMutationResults) und runEnd(). Eine Klasse kann auf mehrere ClassMutationResults-Instanzen verteilt sein. Daher darf ein einzelner Callback nicht als vollständiges Klassenergebnis behandelt werden. [S3]

JaCoCo stellt die Analyse von Class-Dateien und Methodencounter bereit. Der Analyzer behandelt auch Klassen ohne vorhandene Ausführungsdaten; die statische Komplexität ist damit ohne zusätzlichen Coverage-Lauf ableitbar. Für die Berechnung ist der gesamte Complexity-Counter zu verwenden, nicht dessen abgedeckter Anteil. [S6][S7]

Noch kein ausgeführter Integrationsnachweis liegt dafür vor, dass die hier geplante Kombination mit einer konkreten Versionenauswahl in Gradle funktioniert. Diesen Nachweis musst du als ersten Implementierungsschritt liefern.

3.2 Versionen reproduzierbar auswählen

Prüfe die zum Implementierungszeitpunkt verfügbaren stabilen Versionen von PIT, info.solidsoft.pitest, JaCoCo, dem PIT-JUnit-5-Plugin, JUnit und Gradle. Wähle eine tatsächlich kompatible Kombination und pinne sie einschließlich Gradle Wrapper und Java Toolchain.

Unterscheide Build-JDK, Laufzeit-JDK des PIT-Prozesses und Bytecode-Zielversion. Behaupte keine Kompatibilität allein aufgrund einer Abhängigkeitsauflösung oder eines erfolgreichen Kompiliervorgangs.

Die Quellen am Ende dieses Auftrags verweisen teilweise auf master oder trunk. Sie dienen zur Orientierung; überprüfe die benötigten APIs am gewählten Release-Tag oder an dessen veröffentlichten Artefakten. Keine dynamischen Versionen oder Snapshot-Abhängigkeiten für den regulären Build. Dokumentiere die erste erfolgreich getestete Versionenkombination in docs/compatibility.md.

3.3 Minimaler Durchstich

Baue ein kleines Java-Anwenderprojekt mit einer verzweigenden Methode und JUnit-Jupiter-Tests. Lade das wirklich gebaute Plugin-JAR über den PIT-Tool-Classpath. Ein Test, der nur direkt die Listenerklasse instanziiert, reicht dafür nicht.

Der Durchstich muss nachweisen, dass:

  1. PIT die Factory aus dem ausgelieferten Service-Eintrag findet und MUTATION_CRAP aktiviert.
  2. Mutationsergebnisse mit vollständiger Methodenidentität ankommen.
  3. das Plugin genau die zugehörigen originalen Class-Dateien erhält und JaCoCo sie analysiert.
  4. beide Datenquellen eindeutig zusammengeführt werden und ein Report im vorgesehenen Ausgabeverzeichnis entsteht.
  5. normale PIT-Ausgaben parallel möglich bleiben und keine zusätzliche Testausführung für JaCoCo stattfindet.

Verwende zunächst eine einzige unterstützte Versionenkombination. Erst danach eine kleine, begründete Kompatibilitätsmatrix ergänzen.

3.4 Bytecodezugriff nicht voraussetzen

ListenerArguments.getLocator() ist kein Bytecodezugriff: Es liefert im untersuchten Stand einen SourceLocator, der Quelltext als Reader sucht. Über diese API dürfen keine .class-Bytes erwartet werden. [S4]

Der untersuchte Stand bietet dagegen über ListenerArguments.data() Zugriff auf ReportOptions; dort existieren unter anderem Classpath- und Codepath-Informationen. Das ist ein Kandidat für einen gekapselten Adapter, noch keine Zusage für jede PIT-Version. [S4][S5]

Prüfe in dieser Reihenfolge:

  1. Gibt es in der gewählten PIT-Version einen geeigneten verfügbaren Bytecode-/Classpath-Zugang für das Ergebnis-Plugin?
  2. Lassen sich ansonsten die im PIT-Aufruf vorhandenen Classpath- und Codepath-Daten verwenden, ohne PITs private Felder oder Reflection-Hacks zu benötigen?
  3. Falls nötig: Lass eine dünne Gradle-Integration explizit die kompilierten Produktionsausgaben und erforderlichen JAR-Pfade an das Plugin übergeben. Leite diese aus dem tatsächlichen Buildmodell ab; hardcodiere keine build/classes/java/main-Annahme.

Lade die Zielklassen nicht per Reflection, um ihre Bytes zu untersuchen. Analysiere keine mutierten Klassen und keine zusätzlich für Coverage instrumentierte Fassung. Erhalte Classpath-Reihenfolge, Klassenfilter und die eindeutige Herkunft der Bytes.

Ein ergänzender PIT-Erweiterungspunkt ist nur nach einem konkreten Nachweis der Notwendigkeit zu erwägen. Er darf keine versteckte Pflichtänderung am PIT-Core einführen. Ist keine tragfähige Lösung innerhalb des Rahmens möglich, liefere den reproduzierbaren Blocker, den funktionsfähigen Teil und eine kleine begründete Architekturentscheidung. Wechsle nicht stillschweigend zu einem XML-Tool und verkaufe es als das bestellte Plugin.

Halte das Ergebnis dieses Durchstichs in docs/adr/0001-pit-integration.md fest: Versionen, API-Zugang, Packaging, getesteter Ablauf und verbleibende Risiken.

4. Architektur und Datenermittlung

4.1 Verantwortlichkeiten

Trenne fachlich mindestens diese Verantwortlichkeiten, ohne für jede davon ein eigenes Gradle-Subprojekt anzulegen:

Baustein Aufgabe
Metrikkern Methodenidentitäten, Statuspolicy, Berechnung und Gate-Entscheidung; unabhängig von PIT- und Gradle-Typen
JaCoCo-Adapter Originalbytecode analysieren und Methodenkomplexität liefern
PIT-Adapter Factory/Listener, Konfiguration, Klassenherkunft, Ergebnisaggregation und Lebenszyklus
Reportausgabe Versioniertes JSON, Markdown, Zusammenfassung
Gradle-Anbindung Plugin laden, Konfiguration durchreichen und optional ein Gate nach erfolgreichem PIT-Lauf ausführen

Ein oder zwei Produktionsmodule reichen zunächst; ergänze ein dünnes Gradle-Modul nur bei tatsächlichem Nutzen. PIT-spezifische und JaCoCo-spezifische Datentypen sollen nicht durch den gesamten Metrikkern weitergereicht werden.

4.2 Herkunft der Komplexität

Verwende die öffentliche JaCoCo-Analyse-API mit einem leeren ExecutionDataStore, einem Analyzer und einem geeigneten Coverage-Visitor, beispielsweise CoverageBuilder. Lies für jede auswertbare IMethodCoverage die Gesamtkomplexität über getComplexityCounter().getTotalCount() aus. Der Methodenname und getDesc() dienen der Zuordnung. [S6][S7]

Definiere die Größe ausdrücklich als JaCoCo-Zyklomatik, nicht als Cognitive Complexity und nicht als beliebige quelltextbasierte McCabe-Implementierung. JaCoCo verwendet B - D + 1; Exception-Handling-Kanten erhöhen in dieser Definition die Komplexität nicht. Konstruktoren und statische Initialisierer können als JVM-Methoden vorkommen. [S8]

JaCoCos Filter für compilererzeugten Code beeinflussen die Analyse. Übernimm keine eigenen, scheinbar präziseren Quelltextwerte und implementiere keinen alternativen Kontrollflussgraphen. Weise die JaCoCo-Version im Report aus. Insbesondere bei Kotlin können sich Filter und damit Ergebnisse zwischen Versionen verändern. [S12]

Analysiere jede benötigte Klassenversion höchstens einmal pro PIT-Lauf. Vermeide globale statische Caches über mehrere Builds hinweg. Verwende für die Herkunftsprüfung einen Hash der analysierten Class-Datei oder eine gleichwertig belastbare Identität.

4.3 Methodenidentität

Die kanonische Identität innerhalb einer Analyse besteht aus:

  • JVM-Klassenname in einer dokumentierten Normalform;
  • JVM-Methodenname, einschließlich spezieller Namen wie <init>;
  • vollständigem JVM-Descriptor, einschließlich Rückgabetyp.

Normalisiere Klassenbezeichnungen mit Punkt beziehungsweise Schrägstrich an genau einer Adaptergrenze. Innere Klassen behalten ihre JVM-Identität. Verwende weder Quelltextzeilen noch den Methodennamen allein als Schlüssel. PITs Location enthält diese identifizierenden Informationen. [S10]

Außerhalb einer einzelnen Analyse ergänze die Identität um den Analysekontext, etwa Gradle-Projektpfad plus PIT-Taskpfad. Gleiche Klassen- und Methodennamen aus unterschiedlichen Modulen dürfen nicht zusammenfallen.

Quelltextdatei, Zeilennummern und eine lesbare Signatur sind optionale Anzeigeinformationen. Ihr Fehlen darf einen sonst möglichen JVM-Join nicht verhindern.

4.4 Auswertungsscope und Methoden ohne Ergebnisse

Bewerte ausschließlich den ausdrücklich gewählten Produktionscode. Scanne nicht den vollständigen Test- und Abhängigkeitsclasspath als zu bewertende Anwendung.

Erstelle für diesen Scope soweit möglich ein Methodeninventar aus den originalen Produktionsklassen. Respektiere dabei die Klassen-/Methodenauswahl des PIT-Laufs und die eigenen dokumentierten Ausschlüsse. Ein Listener allein liefert keine verlässliche vollständige Liste sämtlicher Methoden des Projekts; die Vollständigkeit muss aus der Klassenauswahl und deren Analyse nachgewiesen werden.

Unterscheide unbedingt:

  • Methode mit einem oder mehreren NO_COVERAGE-Mutanten;
  • Methode ohne Mutationsergebnisse;
  • Methode mit ausschließlich aus der Scoreberechnung ausgeschlossenen Mutanten;
  • nicht eindeutig zuordenbare oder von JaCoCo nicht auswertbare Methode.

Fehlende Mutanten sind keine 100-Prozent-Testwirksamkeit und auch kein Mutation Score von null. Ist eine Methode im Inventar, hat aber keine Ergebnisse, bekommt sie einen nicht numerischen Auswertungszustand. Behaupte ohne weitere Informationen nicht, ob der Grund ein PIT-Filter, fehlende Mutationsmöglichkeiten oder eine begrenzte Analyse war.

Wenn eine vollständige Inventarisierung nicht zuverlässig möglich ist, kennzeichne den Scope als unvollständig. Ein Report darf dann nicht vorgeben, alle Methoden des Projekts zu bewerten. Das aktivierte strikte Gate muss einen unerwartet unvollständigen Scope als Analyseproblem behandeln.

Ein explizit auf bestimmte Klassen beschränkter, innerhalb dieser Auswahl vollständiger Lauf ist dagegen kein Fehler. Seine begrenzte Aussage muss im Report sichtbar sein.

4.5 Ergebnisaggregation und Nebenwirkungen

Sammle Ergebnisse bis zum Ende des Laufs. Mehrere Ergebnisblöcke einer Klasse müssen zusammengeführt werden; die Reihenfolge darf den Report nicht beeinflussen. [S3]

Identische erneut gelieferte Mutationsergebnisse anhand der PIT-Mutantenidentität nicht doppelt zählen. Widersprüchliche Endergebnisse derselben Identität sind ein Analyseproblem, kein Anlass für „last write wins“. Verwechsle vorläufige interne Zustände nicht mit zusätzlichen Mutanten.

Prüfe das tatsächliche Threading-Modell der gewählten API. Verwende einen pro Lauf isolierten Zustand und sichere ihn bei Bedarf gegen nebenläufige Aufrufe. Speichere möglichst Identitäten und Zähler statt unnötig vollständiger PIT-Objektgraphen.

Das Plugin ist ein Beobachter: Es darf weder Mutanten herausfiltern noch deren Status verändern, Testauswahl überschreiben oder PITs Mutator-Konfiguration austauschen. Eigene Reporting-Ausschlüsse wirken nur auf diese Metrik.

4.6 Packaging

Registriere die Factory korrekt unter META-INF/services/org.pitest.mutationtest.MutationResultListenerFactory. Stelle sicher, dass die zur Laufzeit benötigten Abhängigkeiten mitgeladen werden. PIT unterscheidet Tool-Classpath und Classpath des untersuchten Projekts. [S1]

Bündle keine zweite PIT-Distribution in das Plugin. Prüfe mögliche Versionskonflikte insbesondere mit ASM, JaCoCo und anderen PIT-Plugins. Shading/Relocation nur bei nachgewiesenem Bedarf; Service-Einträge und Lizenzhinweise müssen erhalten bleiben.

Ein Packaging-Test muss das veröffentlichungsfähige JAR samt Metadaten und Laufzeitabhängigkeiten aus einem isolierten lokalen Test-Repository konsumieren. Verlasse dich nicht auf zufällige Klassen aus der Entwicklungsumgebung.

5. Exakte Definition der ersten Metrikversion

5.1 Formel

Für eine auswertbare Methode m gelten:

C(m) = zyklomatische Komplexität nach JaCoCo, mindestens 1

K(m) = Anzahl der Mutanten mit Status KILLED

S(m) = Anzahl der Mutanten mit Status SURVIVED

N(m) = Anzahl der Mutanten mit Status NO_COVERAGE

E(m) = K(m) + S(m) + N(m)

Wenn E(m) > 0 ist und keine in Abschnitt 5.2 definierte Uneindeutigkeit vorliegt:

MS(m) = K(m) / E(m)

mutationCrap(m) = C(m)^2 × (1 − MS(m))^3 + C(m)

MS ist ein Verhältnis im Intervall [0, 1], kein Prozentwert im Intervall [0, 100]. Verwende keine ganzzahlige Division. Berechne mit hinreichender numerischer Genauigkeit; runde erst für die Anzeige, nicht vor der Grenzwertprüfung.

Definiere eine nachvollziehbare numerische Vergleichsstrategie. Ein mathematisch auf dem Grenzwert liegendes Ergebnis darf nicht aufgrund eines vermeidbaren Gleitkommaartefakts durchfallen. Da die Eingangszähler ganzzahlig sind, ist auch ein Vergleich anhand der rationalen Formel möglich. Du musst dafür kein allgemeines Numerikframework bauen.

C K S N MS Mutation-CRAP
1 0 1 0 0 2
10 0 10 0 0 110
10 5 5 0 0.5 22.5
10 8 2 0 0.8 10.8
10 10 0 0 1 10
30 10 0 0 1 30

Diese Zahlen sind berechnete Testorakel, keine empirischen Qualitätsklassen. Bei vollständiger Mutantenerkennung entspricht der Score weiterhin der Komplexität. Eine Methode mit C > Grenzwert kann das entsprechende Gate daher auch mit MS = 1 nicht bestehen.

5.2 Statuspolicy strict-v1

Verwende eine explizite Zuordnung. Übernimm nicht pauschal DetectionStatus.isDetected() als Zähler: Im untersuchten PIT-Stand ist dieses Flag auch für verschiedene Fehlerzustände und EQUIVALENT wahr. [S11]

PIT-Status Behandlung für diese Metrik
KILLED In Zähler und Nenner
SURVIVED Nur im Nenner
NO_COVERAGE Nur im Nenner; nicht herausfiltern
EQUIVALENT, sofern die PIT-Version diesen Status liefert Weder Zähler noch Nenner; separat ausweisen
NON_VIABLE Weder Zähler noch Nenner; separat ausweisen
TIMED_OUT Uneindeutig: betroffene Methode erhält keinen regulären Score
MEMORY_ERROR, RUN_ERROR Uneindeutig: betroffene Methode erhält keinen regulären Score
NOT_STARTED, STARTED, falls am Ende noch vorhanden Unvollständig: betroffene Methode erhält keinen regulären Score
Jeder nicht ausdrücklich unterstützte Status Nicht stillschweigend werten; als Analyseproblem ausweisen

Diese konservative Policy ist eine Projektentscheidung, keine Behauptung über PITs eigene Scoredefinition. Ein Timeout kann durch eine wirksam erkannte Endlosschleife entstehen, ist hier aber ohne zusätzliche Evidenz nicht gleichbedeutend mit einem durch eine Assertion getöteten Mutanten. [S11]

Ein uneindeutiger Endstatus macht den regulären Score der betroffenen Methode ungültig, selbst wenn es daneben genügend KILLED- oder SURVIVED-Ergebnisse gibt. Andernfalls könnte das Weglassen problematischer Ergebnisse den Score verbessern. Die Rohzähler bleiben sichtbar.

Erfinde keine automatische Äquivalenzerkennung. Nur tatsächlich so klassifizierte Ergebnisse dürfen unter EQUIVALENT erscheinen. Das Vorhandensein dieses Status in einer API bedeutet nicht, dass PIT jeden äquivalenten Mutanten erkennen kann.

Halte Statusmapping und Formel getrennt und teste sie separat. Eine spätere Timeout-Policy darf ergänzt werden, benötigt aber eine andere Policykennung und darf nicht unbemerkt unter strict-v1 laufen.

5.3 Nicht numerische Auswertungszustände

Verwende mindestens unterscheidbare Zustände mit maschinenlesbaren Gründen:

  • SCORED: gültige Komplexität und auswertbare Mutanten vorhanden.
  • NO_MUTATION_RESULTS: Methode im Inventar, aber ohne Mutationsergebnisse.
  • NO_ELIGIBLE_MUTANTS: Ergebnisse vorhanden, aber nach der Policy kein Mutant im Nenner.
  • INCONCLUSIVE: uneindeutige, unvollständige, unbekannte oder widersprüchliche Ergebnisse.
  • COMPLEXITY_UNAVAILABLE: Ergebnis vorhanden, aber Komplexität beziehungsweise eindeutige Zuordnung fehlt.
  • EXCLUDED: ausdrücklich von dieser Auswertung ausgenommen.

Nicht auswertbare Werte sind im JSON null, nicht 0, nicht 100 %, nicht NaN und nicht Infinity. Eine gültig ermittelte Komplexität darf auch bei fehlendem Mutation Score ausgewiesen werden.

Abstrakte und native Methoden ohne analysierbaren Methodenrumpf werden nicht mit einer erfundenen Komplexität bewertet. Für compilererzeugte oder durch JaCoCo gefilterte Methoden darf ein begründeter Ausschluss verwendet werden, sofern er nachgewiesen und im Report erkennbar ist. Eine beliebige fehlgeschlagene Zuordnung darf nicht als „generated“ wegdefiniert werden.

5.4 Aussagegrenzen

Zeige neben dem Score immer Komplexität, Mutation Score und Mutantenzahl. Ein Verhältnis aus einem einzelnen Mutanten ist keine gleich starke Evidenz wie dasselbe Verhältnis aus vielen Mutanten; die Metrik modelliert diese Unsicherheit nicht statistisch.

Vergleiche Werte nur unter Berücksichtigung von Mutatoren, Filtern, Testauswahl, Bytecode und Toolversionen. Eine Änderung dieser Bedingungen kann den Score verändern, ohne dass Produktions- oder Testcode verbessert wurde. Ein guter Score ist weder ein Korrektheitsbeweis noch ein Ersatz für Review.

Der verwendete Score schließt NO_COVERAGE ein. Er ist daher nicht einfach mit einem Wert gleichzusetzen, der nur bereits abgedeckte Mutanten betrachtet; PIT beschreibt die entsprechenden Ergebnisunterschiede in seinen Grundlagen. [S13]

6. Reports, Konfiguration und Gradle-Gate

6.1 JSON als belastbarer Vertrag

Liefere mutation-crap.json mit dokumentiertem JSON-Schema. Mindestens enthalten sein müssen:

Ebene Inhalte
Definition schemaVersion, metricId, metricVersion, scorePolicyId, Kennung und Version des Komplexitätsproviders
Herkunft Plugin- und PIT-Version, Analyse-/Projektkontext, relevante Eingabeidentitäten
Scope ausgewählte Produktionsklassen beziehungsweise Scope-Beschreibung, Inventarvollständigkeit, Ausschlüsse
Konfiguration wirksame metrikeigene Optionen; verfügbare PIT-Mutator-/Filter-/Testauswahl oder ausdrücklicher Hinweis auf fehlende Provenienz
Laufstatus regulär abgeschlossener Report oder unvollständige Analyse; Diagnosegründe
Methode kanonische ID, gegebenenfalls Anzeigeinformationen, Komplexität, Rohzähler je Status, Nenner, Mutation Score, Mutation-CRAP, Auswertungszustand und Gründe
Zusammenfassung Anzahl bewerteter, unbewerteter, uneindeutiger und ausgeschlossener Methoden; größte Scores und gegebenenfalls Gate-Verletzungen

Ein Konfigurationsfingerprint darf nur als vollständig bezeichnet werden, wenn die dafür relevanten Einstellungen tatsächlich bekannt sind. Dump nicht unkontrolliert sämtliche JVM-Properties oder Umgebungsvariablen; darunter könnten Zugangsdaten sein.

Sortiere Ausgaben deterministisch. Für dieselben Eingabedaten und dieselbe Konfiguration müssen die fachlichen Reportdaten identisch sein. Zeitstempel und Lauf-IDs sind, sofern vorhanden, ausdrücklich davon getrennte Metadaten.

Schreibe Reports so, dass ein abgebrochener Lauf nicht versehentlich einen alten erfolgreichen Report als seinen eigenen hinterlässt. Verwende beispielsweise ein laufbezogenes Zwischenartefakt und einen erst bei erfolgreichem Abschluss veröffentlichten finalen Report. Ein vorhandener Dateiname allein ist kein Abschlussnachweis.

6.2 Lesbare CI-Ausgabe

Liefere mutation-crap.md mit einer kurzen Zusammenfassung und einer nach absteigendem Score sortierten Methodenübersicht. Zeige unbewertbare Methoden und Diagnosegründe in einem eigenen Abschnitt. Include- und Exclude-Regeln müssen nachvollziehbar bleiben.

Die Konsole zeigt einen begrenzten Ausschnitt und den Reportpfad, nicht tausende Zeilen. Markdown, JSON und Konsole müssen dieselbe Berechnung verwenden. Normale PIT-HTML-/XML-Reports dürfen nicht überschrieben werden.

Eine HTML-Ausgabe oder JUnit-XML für CI-Systeme ist später möglich, aber keine Voraussetzung für den ersten Abschluss.

6.3 Konfiguration

Dokumentiere eine kleine, eindeutig benannte Konfiguration mit mindestens:

  • Aktivierung des Ausgabeformats und Reportziel über den unterstützten PIT-/Gradle-Weg;
  • optionalem Score-Gate und dessen maximalem Methodenwert;
  • Reporting-Ausschlüssen für Klassen und Methoden, mit definierter Mustersemantik;
  • Analysekontext für Teilprojekt beziehungsweise PIT-Task;
  • bei Bedarf expliziten Originalbytecodepfaden;
  • ausdrücklicher Freigabe leerer Auswertungen für absichtlich leere Module.

Halte Gate-Optionen in der Gradle-Anbindung, wenn sie dort gebraucht werden; die gesamte Konfiguration muss nicht künstlich durch PIT geschleust werden. Werte ungültige Zahlen, widersprüchliche Optionen und unbekannte metrikeigene Konfigurationsschlüssel nicht stillschweigend aus.

Schließe triviale Methoden nicht automatisch aufgrund von Namen wie get* aus. Nutzer dürfen solche Ausschlüsse konfigurieren; standardmäßig sollen echte auswertbare Methoden sichtbar bleiben. Synthetisch bedeutet insbesondere bei Lambdas nicht automatisch fachlich unwichtig.

6.4 Gate nach abgeschlossenem PIT-Lauf

Bevorzuge einen dünnen Gradle-Verification-Task, etwa mutationCrapCheck, der von der vorhandenen PIT-Task abhängt und den eigenen fertigen JSON-Report auswertet. Das ist kein Wechsel zur Auswertung des PIT-XMLs.

Die Metrikberechnung bleibt im gemeinsamen Kern beziehungsweise PIT-Plugin. Der Gradle-Task enthält keine zweite Formelimplementierung. Er wertet den Reportvertrag mit einer gemeinsamen Gate-Logik aus.

Begründung dieser Architekturentscheidung: Ein Abbruch direkt in runEnd() eines Listeners könnte die Finalisierung anderer Reports beeinträchtigen. Belege einen solchen direkten Fehlerpfad durch Integrationstests, falls du statt des nachgeschalteten Tasks dennoch diesen Weg wählst. Der sichere Ablauf ist: PIT beendet sich regulär, Reports sind fertig, danach entscheidet das eigene Gate.

Die Gate-Regeln der ersten Version:

  1. Ohne aktiviertes Gate kein Build-Abbruch allein wegen hoher Scores.
  2. Bei aktivem Gate verletzt eine bewertete, nicht ausgeschlossene Methode die Grenze genau dann, wenn mutationCrap > maxMutationCrap. Gleichheit besteht.
  3. INCONCLUSIVE, unerwartet fehlende Komplexität, unerwartet unvollständiger Scope oder ein unbrauchbarer Report lassen das strikte Gate als Analysefehler scheitern. Das ist keine behauptete schlechte Codequalität.
  4. NO_MUTATION_RESULTS und NO_ELIGIBLE_MUTANTS sind sichtbar unbewertet. Sie verletzen allein keinen numerischen Grenzwert. Das Gate darf sie nicht als erfolgreich getestete Methoden darstellen.
  5. Hat eine aktiv geprüfte Analyse insgesamt keine einzige bewertbare Methode, scheitert sie als leere Auswertung, außer dies ist ausdrücklich erlaubt.
  6. Fehlgeschlagene PIT-Ausführung, fehlender/abgebrochener Report oder nicht unterstützte Schemaversion dürfen nicht zu einem grünen Gate durch Lesen einer alten Datei führen.

In CI müssen die bereits erstellten Reports auch bei einer Grenzwertverletzung als Artefakte verfügbar bleiben. Hänge die teure PIT-Analyse nicht ungefragt an jeden normalen check-Aufruf; dokumentiere die ausdrückliche Aktivierung des Mutation-Gates im CI-Workflow.

6.5 Gradle-Anwenderintegration

Nutze zunächst das bestehende info.solidsoft.pitest. Dessen Dokumentation beschreibt das Laden generischer PIT-Plugins sowie zusätzliche Source-Sets und Multi-Project-Konfigurationen. Diese Konfigurationen müssen nicht durch eine eigene PIT-Orchestrierung ersetzt werden. [S9]

Liefere mindestens ein lauffähiges Java-Beispiel mit Kotlin DSL und eine überprüfte Groovy-DSL-Anleitung. Falls ein eigenes dünnes Gradle-Plugin erforderlich oder sinnvoll ist, beschränke es auf die oben genannten Integrationsaufgaben.

Vorhandene Unit- und Integrationstest-Source-Sets müssen über die tatsächliche PIT-Konfiguration verwendbar bleiben. Führe sie nicht selbst über einen zusätzlichen JaCoCo- oder Gradle-Testlauf aus. Einbezogene Tests werden von PIT im Rahmen seiner normalen Arbeitsweise ausgeführt.

In Multi-Project-Builds zunächst getrennte Reports und Gates pro Analyse liefern. Reportpfade dürfen bei parallelen Teilprojekten nicht kollidieren. Ein Root-Task darf diese Teilprüfungen koordinieren, ohne Methodenwerte oder Mutation Scores zu einem neuen Gesamtwert zu vermischen.

Deklariere für eigene Gradle-Tasks Eingaben, Ausgaben und Konfigurationsabhängigkeiten korrekt. Eine Änderung des Gate-Grenzwerts muss neu geprüft werden, benötigt aber keinen erneuten Mutationstest, wenn ein für dieselben Analyseinputs gültiger PIT-Report vorliegt. Verifiziere dieses Verhalten; verlasse dich nicht auf bloße Dateiexistenz.

7. Kotlin/JVM: begrenztes, nachgeordnetes Ziel

Nach dem Java-Durchstich ergänze ein kleines Kotlin/JVM-Fixture mit einer gewöhnlichen Methode, Verzweigungen und überladenen Funktionen. Prüfe die Zuordnung anhand tatsächlicher JVM-Namen und Descriptoren.

Betrachte Default-Argumente, Top-Level-Funktionen, Properties, Lambdas, Bridge-/synthetische Methoden, Inline-Funktionen und Suspend-Funktionen anschließend als gesonderte Kompatibilitätsfälle. Eine Quelltextfunktion muss nicht genau einer JVM-Methode entsprechen. Fasse mehrere JVM-Methoden nicht allein aufgrund gleicher Quelltextzeilen zusammen.

JaCoCo besitzt Kotlin-spezifische Filter; PIT verweist zusätzlich auf erweiterte Kotlin-Unterstützung durch Arcmutate. Daraus folgt weder eine Garantie für alle Kotlin-Konstrukte im gewählten Setup noch, dass gewöhnlicher Kotlin-Bytecode grundsätzlich nicht funktionieren kann. Prüfe das konkrete freie Setup. [S12][S14]

Kennzeichne das Ergebnis präzise, beispielsweise „Kotlin/JVM-Basismethoden mit den dokumentierten Compiler-/PIT-Versionen getestet“. Kein pauschales „Kotlin vollständig unterstützt“ aufgrund eines einzelnen grünen Tests. Nicht abbildbare Fälle erhalten eine dokumentierte Einschränkung oder einen sichtbaren nicht numerischen Zustand, keinen erfundenen Score.

Ein belegter Kotlin-Blocker darf den Java-MVP nicht blockieren. Der Abschlussbericht muss dann den tatsächlich getesteten Stand nennen.

8. Arbeitsweise, Tests und Lieferumfang

8.1 Gherkin sinnvoll einsetzen

Die folgenden Features beschreiben beobachtbare Fähigkeiten aus Sicht von Entwicklern und CI, nicht betriebswirtschaftliche Vorgänge. Das passt zu Gherkins Feature-/Scenario-Modell. Interne Klassenstrukturen oder private Methoden gehören dagegen nicht in diese Akzeptanzszenarien. [S15]

Übernimm die Featureblöcke aus Abschnitt 9 in einzelne .feature-Dateien unter docs/features/. Die Tags @MC-... sind stabile Anforderungs-IDs. Implementiere die zugehörigen automatisierten Nachweise mit JUnit Jupiter; ein Cucumber-Laufzeitframework ist nicht vorgeschrieben.

Führe docs/acceptance-test-map.md, das jede ID auf konkrete Testklasse/Testmethode und ihren Status abbildet. Eine syntaktisch gültige Featuredatei ist noch kein Testnachweis. Wo kein Gherkin-Runner eingesetzt wird, sage ausdrücklich, dass JUnit-Tests die Szenarien prüfen. Prüfe die Feature-Syntax mit einem geeigneten Parser oder einem gleichwertigen reproduzierbaren Test.

Die mit @optional-kotlin markierten Szenarien gehören nicht zum blockierenden Java-MVP. Alle anderen Szenarien sind verbindliche Akzeptanzanforderungen; Tabellenzeilen eines Szenariogrundrisses sind getrennte Testfälle.

8.2 Testebenen

Metrikkern: Formelwerte, Grenzgleichheit, alle Statusfälle, unzulässige Eingangswerte, leere Nenner und Eigenschaften wie C <= mutationCrap <= C² + C für gültige Werte. Für konstantes C darf der Score bei steigendem MS nicht steigen.

Bytecode-/Adaptertests: Kompilierte Java-Fixtures mit Verzweigungen, Schleifen, Switch, Überladungen, Konstruktoren, Lambdas sowie abstrakten/native Methoden. Prüfe auch Klassen ohne Debuginformationen. Teste die notwendige Normalisierung und bewusst fehlende oder falsche Class-Dateien. Bekannte kleine Referenzwerte und JaCoCo-Counter dienen als Orakel; keine zweite eigene Complexity-Implementierung aufbauen.

Listener-/Reporttests: Gestückelte und umgeordnete Ergebnisse, Duplikate, widersprüchliche Status, stabile Ausgabe, Escaping, Schema, Abbruch-/Fehlerfälle und sichere Finalisierung.

Gradle-Funktionstests: Nutze nach Möglichkeit Gradle TestKit und reale kleine Anwenderprojekte. TestKit ist für Tests von Buildlogik vorgesehen. [S16] Prüfe insbesondere das gepackte Plugin, JUnit-Jupiter-Integration, beide Gate-Ausgänge, zusätzliche Test-Source-Sets und zwei Teilprojekte.

Optionaler Eigen-Mutationstest: PIT für den Metrikkern ist sinnvoll, sobald der normale Build steht. Vermeide rekursive Builds, bei denen der Eigen-Mutationstest wieder die vollständigen PIT-Anwender-Funktionstests startet. Kein pauschales 100-Prozent-Mutationsziel ohne Behandlung äquivalenter oder ungeeigneter Mutanten.

8.3 Umsetzungsschritte

  1. Repository und Gradle Wrapper aufsetzen, kompatible Versionen pinnen, minimalen realen Durchstich umsetzen und ADR schreiben.
  2. Metrikkern mit Referenzwerten und strict-v1 testgetrieben implementieren.
  3. Originalbytecode, Methodeninventar und JaCoCo-Komplexität zuverlässig anbinden.
  4. PIT-Ergebnisse vollständig aggregieren und Identitäts-/Fehlerfälle testen.
  5. JSON-Schema, JSON-/Markdown-Report und Konsolenzusammenfassung liefern.
  6. Optional einschaltbares Gradle-Gate und Multi-Project-Fixture fertigstellen.
  7. Kotlin-Basissupport prüfen und Grenzen dokumentieren.
  8. Packaging aus isoliertem Test-Repository prüfen, Dokumentation vervollständigen und tatsächliche Testresultate zusammenfassen.

Setze offene, nicht blockierende Details begründet selbst fest, statt die Umsetzung mit einer langen Liste von Rückfragen zu stoppen. Halte Architekturentscheidungen fest und behalte die Fachsemantik dieser Spezifikation bei.

8.4 Erwartete Repository-Artefakte

Liefere mindestens Produktionscode, Tests, Wrapper/Builddateien, korrekt gepacktes PIT-Plugin, Beispielprojekt und eine CI-taugliche lokale Buildanleitung. Hinzu kommen:

  • README.md: Zweck, experimenteller Status, Installation, Gradle-Konfiguration, Reports, Gate und ein vollständiger Quickstart.
  • docs/metric.md: Formel, strict-v1, N/A-Zustände, Grenzwerte und Aussagegrenzen.
  • docs/compatibility.md und das Integrations-ADR.
  • docs/features/*.feature und docs/acceptance-test-map.md.
  • Versioniertes JSON-Schema sowie kleine erwartete Beispielreports.
  • Dokumentierter Umgang mit Runtime-Abhängigkeiten und deren Lizenzhinweisen.

Wähle ohne bekannte Eigentümerangaben keine fremde reale Group-ID oder reservierte Plugin-ID. Verwende nachvollziehbare Platzhalter beziehungsweise einen lokalen Entwicklungsnamensraum. Lege eine Projektlizenz nicht im Namen des Eigentümers fest, falls sie nicht vorgegeben ist; Abhängigkeitshinweise sind dennoch zu dokumentieren.

8.5 Definition of Done

Der Java-MVP ist fertig, wenn ein sauberes Checkout mit dokumentierten Voraussetzungen reproduzierbar baut, das gepackte Plugin in einem realen Gradle/PIT-Lauf funktioniert und alle verbindlichen Szenarien automatisiert nachgewiesen sind. Reports müssen echte pro-Methode-Werte aus PIT und JaCoCo enthalten, nicht nur aus simulierten Testdaten.

Berichte am Ende, was implementiert und mit welchen Versionen getestet wurde, welche Befehle tatsächlich ausgeführt wurden, welche Akzeptanztests bestanden haben und welche Einschränkungen bleiben. Behaupte weder ausgeführte Tests ohne Ausführung noch Kompatibilität ohne Nachweis. Eine fehlende lokale Voraussetzung oder ein echter API-Blocker ist konkret mit Reproduktionsschritten anzugeben.

9. Gherkin-Features und Akzeptanzszenarien

Die folgenden Blöcke sind in jeweils eine eigene Featuredatei zu übernehmen. Englische Status- und Feldnamen sind technische Identifikatoren; die Szenariosprache ist Deutsch.

9.1 01-metric.feature

# language: de
Funktionalität: Mutation-CRAP pro JVM-Methode berechnen
  Als Entwickler möchte ich Komplexität und Mutationsergebnisse gemeinsam sehen,
  damit ich Methoden für eine genauere Prüfung priorisieren kann.

  @MC-001
  Szenariogrundriss: Der Score entspricht der definierten Formel
    Angenommen eine Methode hat die JaCoCo-Komplexität <komplexitaet>
    Und es liegen <killed> KILLED-, <survived> SURVIVED- und <uncovered> NO_COVERAGE-Ergebnisse vor
    Und es liegen keine weiteren Mutationsergebnisse vor
    Wenn die Methode mit der Policy "strict-v1" ausgewertet wird
    Dann ist ihr Zustand "SCORED"
    Und ihr Mutation Score ist <mutationScore>
    Und ihr Mutation-CRAP ist <crap>

    Beispiele:
      | komplexitaet | killed | survived | uncovered | mutationScore | crap |
      | 1            | 0      | 1        | 0         | 0             | 2    |
      | 10           | 0      | 10       | 0         | 0             | 110  |
      | 10           | 5      | 5        | 0         | 0.5           | 22.5 |
      | 10           | 8      | 2        | 0         | 0.8           | 10.8 |
      | 10           | 10     | 0        | 0         | 1             | 10   |
      | 30           | 10     | 0        | 0         | 1             | 30   |

  @MC-002
  Szenario: Ausschließlich nicht wertbare Mutanten ergeben keinen Score
    Angenommen eine Methode hat die JaCoCo-Komplexität 4
    Und ihre einzigen Ergebnisse sind 2 EQUIVALENT und 1 NON_VIABLE
    Wenn die Methode mit der Policy "strict-v1" ausgewertet wird
    Dann ist ihr Zustand "NO_ELIGIBLE_MUTANTS"
    Und ihr Mutation Score und ihr Mutation-CRAP sind null
    Und ihre Komplexität und ihre Rohzähler bleiben im Report sichtbar

  @MC-003
  Szenario: Eine Methode ohne Ergebnisse wird nicht als gut getestet dargestellt
    Angenommen eine Methode gehört zum ausgewerteten Methodeninventar
    Und sie hat die JaCoCo-Komplexität 12
    Und es liegt kein Mutationsergebnis für diese Methode vor
    Wenn der Report erstellt wird
    Dann ist ihr Zustand "NO_MUTATION_RESULTS"
    Und ihr Mutation Score und ihr Mutation-CRAP sind null
    Und der Report behauptet weder null noch vollständige Testwirksamkeit

  @MC-004
  Szenariogrundriss: Ungültige Recheneingaben werden nicht kaschiert
    Angenommen eine Recheneingabe enthält <fehler>
    Wenn eine numerische Metrik daraus berechnet werden soll
    Dann wird die ungültige Eingabe mit einem konkreten Grund zurückgewiesen
    Und es wird kein gültiger Methodenwert erzeugt

    Beispiele:
      | fehler                                      |
      | eine negative Anzahl getöteter Mutanten      |
      | einen negativen Komplexitätswert             |
      | Komplexität 0 für eine numerisch bewertete Methode |

9.2 02-status-policy.feature

# language: de
Funktionalität: Mutationsergebnisse nachvollziehbar gewichten
  Als Nutzer möchte ich erkennen, welche Ergebnisse in den Score eingehen,
  damit Ausführungsprobleme nicht als Teststärke erscheinen.

  @MC-005
  Szenario: Nicht abgedeckte Mutanten bleiben im Nenner
    Angenommen eine Methode hat die JaCoCo-Komplexität 10
    Und sie hat 4 KILLED-, 1 SURVIVED- und 5 NO_COVERAGE-Ergebnisse
    Wenn sie mit der Policy "strict-v1" ausgewertet wird
    Dann beträgt der Nenner 10
    Und ihr Mutation Score beträgt 0.4
    Und ihr Mutation-CRAP beträgt 31.6

  @MC-006
  Szenariogrundriss: Explizit ausgeschlossene Status erhöhen nicht die Teststärke
    Angenommen eine Methode hat 1 KILLED- und 1 SURVIVED-Ergebnis
    Und zusätzlich 8 Ergebnisse mit Status <status>
    Wenn sie mit der Policy "strict-v1" ausgewertet wird
    Dann beträgt ihr Mutation Score 0.5
    Und ihr Nenner beträgt 2
    Und die 8 ausgeschlossenen Ergebnisse sind separat ausgewiesen

    Beispiele:
      | status      |
      | EQUIVALENT  |
      | NON_VIABLE  |

  @MC-007
  Szenariogrundriss: Uneindeutige Endergebnisse verhindern einen regulären Score
    Angenommen eine Methode hat 9 KILLED-Ergebnisse
    Und am Ende der Analyse ein Ergebnis mit Status <status>
    Wenn sie mit der Policy "strict-v1" ausgewertet wird
    Dann ist ihr Zustand "INCONCLUSIVE"
    Und ihr Mutation Score und ihr Mutation-CRAP sind null
    Und der auslösende Status und alle Rohzähler sind sichtbar

    Beispiele:
      | status                  |
      | TIMED_OUT               |
      | MEMORY_ERROR            |
      | RUN_ERROR               |
      | NOT_STARTED             |
      | STARTED                 |
      | UNBEKANNTER_NEUER_STATUS |

9.3 03-complexity-and-identity.feature

# language: de
Funktionalität: Komplexität und Mutanten derselben Methode verbinden
  Als Entwickler möchte ich verlässliche Methodenwerte,
  auch bei Überladungen oder fehlenden Quelltextinformationen.

  @MC-008
  Szenario: Komplexität ohne zusätzlichen Coverage-Lauf ermitteln
    Angenommen ein Java-Fixture enthält eine Methode mit einer einfachen if-Verzweigung
    Und die originale Class-Datei liegt vor
    Und es gibt weder eine JaCoCo-exec-Datei noch einen für diese Auswertung aktiven JaCoCo-Agenten
    Wenn das Plugin die Klasse statisch analysiert
    Dann wird für diese Methode die JaCoCo-Gesamtkomplexität 2 ermittelt
    Und dafür wird kein Test gestartet
    Und es wird nicht versehentlich die abgedeckte Komplexität 0 verwendet

  @MC-009
  Szenario: Fehlende Debuginformationen verhindern den Join nicht
    Angenommen dasselbe kleine Java-Fixture wird mit und ohne Debuginformationen kompiliert
    Und für beide Varianten liegen passende Mutationsergebnisse vor
    Wenn beide Varianten jeweils ausgewertet werden
    Dann werden die Mutanten jeweils der korrekten JVM-Methode zugeordnet
    Und die Methode hat in beiden Varianten dieselbe Gesamtkomplexität
    Und fehlende Quelltextzeilen werden als nicht verfügbar dargestellt

  @MC-010
  Szenario: Überladene Methoden bleiben getrennt
    Angenommen eine Klasse besitzt die Methoden "calculate(I)I" und "calculate(Ljava/lang/String;)I"
    Und für beide Methoden liegen verschiedene Komplexitäten und Mutationsergebnisse vor
    Wenn der Report erstellt wird
    Dann enthält er zwei getrennte Methodenidentitäten
    Und jede Methode verwendet nur ihre eigene Komplexität und ihre eigenen Mutanten

  @MC-011
  Szenario: Fehlender Originalbytecode wird nicht durch einen Fantasiewert ersetzt
    Angenommen ein Mutationsergebnis verweist auf eine Methode
    Und deren Originalklasse kann nicht zuverlässig gelesen werden
    Wenn die Analyse abgeschlossen wird
    Dann wird der betroffene Eintrag als "COMPLEXITY_UNAVAILABLE" ausgewiesen
    Und kein Komplexitätswert wird aus Namen oder Mutantenzahl geschätzt
    Und ein aktiviertes striktes Gate scheitert mit einem Analysefehler

  @MC-012
  Szenario: Eine Klasse ohne Ergebnis-Callback bleibt im Inventar sichtbar
    Angenommen eine Produktionsklasse gehört zum ausdrücklich ausgewählten Analysescope
    Und ihre ausführbaren Methoden können statisch analysiert werden
    Und PIT liefert für diese Klasse keine Mutationsergebnisse
    Wenn das Plugin den ausgewählten Scope vollständig inventarisiert
    Dann erscheinen ihre Methoden mit Zustand "NO_MUTATION_RESULTS"
    Und sie werden nicht als erfolgreich mutationsgetestet gezählt

  @MC-013
  Szenario: Reporting-Ausschlüsse verändern PIT nicht
    Angenommen eine Methode ist durch eine ausdrückliche Reporting-Regel ausgeschlossen
    Und PIT hat für diese Methode Mutationsergebnisse erzeugt
    Wenn das Plugin den Report erstellt
    Dann sind Ausschluss und angewendete Regel nachvollziehbar
    Und diese Methode nimmt nicht am Score-Gate teil
    Und die ursprünglichen PIT-Ergebnisse werden weder verändert noch entfernt

9.4 04-pit-lifecycle.feature

# language: de
Funktionalität: Ergebnisse eines PIT-Laufs vollständig und ohne Nebenwirkungen auswerten
  Als CI-Nutzer möchte ich einen einzigen normalen PIT-Lauf verwenden,
  ohne dass das zusätzliche Reporting die Mutationstests verändert.

  @MC-014
  Szenario: Mehrere Ergebnisblöcke derselben Klasse werden zusammengeführt
    Angenommen eine Methode hat die JaCoCo-Komplexität 10
    Und ein Ergebnisblock enthält 4 ihrer KILLED-Ergebnisse
    Und ein späterer Block enthält weitere 4 KILLED- und 2 SURVIVED-Ergebnisse
    Wenn der PIT-Lauf abgeschlossen ist
    Dann beträgt ihr Nenner 10
    Und ihr Mutation Score beträgt 0.8
    Und ihr Mutation-CRAP beträgt 10.8
    Und die Methodenauswertung wird nicht durch den letzten Block überschrieben

  @MC-015
  Szenario: Die Reihenfolge der Ergebnisblöcke beeinflusst das Ergebnis nicht
    Angenommen zwei Auswertungen erhalten dieselben eindeutigen Endergebnisse
    Und die Ergebnisblöcke kommen in unterschiedlicher Reihenfolge an
    Wenn beide Auswertungen abgeschlossen werden
    Dann sind ihre fachlichen JSON-Inhalte identisch
    Und die Methoden erscheinen in derselben stabilen Sortierung

  @MC-016
  Szenario: Wiederholte identische Ergebnisse werden nicht doppelt gezählt
    Angenommen dieselbe PIT-Mutantenidentität wird zweimal mit Endstatus KILLED geliefert
    Wenn die Ergebnisse aggregiert werden
    Dann erhöht diese Identität den KILLED-Zähler nur einmal

  @MC-017
  Szenario: Widersprüchliche Endergebnisse sind ein Analyseproblem
    Angenommen dieselbe PIT-Mutantenidentität wird mit KILLED und mit SURVIVED geliefert
    Wenn die Auswertung abgeschlossen wird
    Dann wird der Widerspruch als "INCONCLUSIVE" dokumentiert
    Und nicht einfach das zuletzt eingegangene Ergebnis verwendet

  @MC-018
  Szenario: Das ausgelieferte Plugin ergänzt einen normalen PIT-Lauf
    Angenommen das Plugin-JAR und seine Abhängigkeiten sind über den PIT-Tool-Classpath geladen
    Und MUTATION_CRAP sowie die normalen PIT-Ausgaben HTML und XML sind aktiviert
    Wenn das Java-Beispielprojekt seine PIT-Analyse ausführt
    Dann entstehen der eigene JSON- und Markdown-Report sowie die normalen PIT-Reports
    Und das Plugin hat keine zusätzliche PIT-Analyse gestartet
    Und das Plugin hat keinen zusätzlichen Coverage-Testlauf gestartet
    Und es hat die Mutantenauswahl nicht verändert

9.5 05-reports-and-gate.feature

# language: de
Funktionalität: Nachvollziehbare Reports und ein bewusst aktiviertes Build-Gate liefern
  Als CI-Nutzer möchte ich hohe Scores und Analyseprobleme unterscheiden,
  ohne fehlende Daten mit guter Qualität zu verwechseln.

  @MC-019
  Szenario: Der Standardmodus erstellt Reports ohne Score-bedingten Build-Abbruch
    Angenommen die Analyse ist regulär abgeschlossen
    Und eine Methode hat Mutation-CRAP 110
    Und es ist kein Score-Gate aktiviert
    Wenn das Ergebnis ausgegeben wird
    Dann bleibt der Build wegen dieses Scores erfolgreich
    Und der hohe Wert ist im Report sichtbar

  @MC-020
  Szenariogrundriss: Nur eine echte Überschreitung verletzt den Grenzwert
    Angenommen eine Methode hat den gültigen Mutation-CRAP-Wert <score>
    Und ein Score-Gate mit Grenzwert 30 ist aktiviert
    Und es liegen keine weiteren Verstöße oder Analyseprobleme vor
    Wenn das Gate ausgewertet wird
    Dann ist das Build-Ergebnis <ergebnis>

    Beispiele:
      | score | ergebnis     |
      | 29    | erfolgreich  |
      | 30    | erfolgreich  |
      | 31    | fehlgeschlagen |

  @MC-021
  Szenario: Anzeigerundung beeinflusst das Gate nicht
    Angenommen eine Methode hat Komplexität 10, 79 KILLED- und 21 SURVIVED-Ergebnisse
    Und ihr exakter Mutation-CRAP-Wert beträgt 10.9261
    Und für die Anzeige wird auf eine Nachkommastelle gerundet
    Und der aktivierte Grenzwert beträgt 10.92
    Wenn das Gate ausgewertet wird
    Dann scheitert es wegen der tatsächlichen Überschreitung
    Und der gerundete Anzeigewert 10.9 wird nicht für den Vergleich verwendet

  @MC-022
  Szenariogrundriss: Ein striktes Gate kann Analyseprobleme nicht als Erfolg werten
    Angenommen alle numerisch bewerteten Methoden liegen unter ihrem Grenzwert
    Aber es besteht <problem>
    Wenn das aktivierte strikte Gate ausgewertet wird
    Dann scheitert es mit einem konkreten Analysefehler
    Und der Fehler wird nicht als schlechte Testqualität bezeichnet

    Beispiele:
      | problem                                      |
      | ein INCONCLUSIVE-Ergebnis                    |
      | unerwartet fehlende Methodenkomplexität       |
      | ein unerwartet unvollständig inventarisierter Scope |
      | ein fehlender oder unlesbarer aktueller Report |
      | eine nicht unterstützte Report-Schemaversion  |

  @MC-023
  Szenario: Eine leere Auswertung wird nicht stillschweigend grün
    Angenommen ein Score-Gate ist aktiviert
    Und im ausgewählten Scope ist keine einzige Methode numerisch bewertbar
    Und leere Auswertungen sind nicht ausdrücklich erlaubt
    Wenn das Gate ausgewertet wird
    Dann scheitert es mit dem Hinweis auf eine leere Auswertung

  @MC-024
  Szenario: Ein absichtlich leeres Modul kann ausdrücklich erlaubt werden
    Angenommen die Analyse eines Moduls ist regulär abgeschlossen
    Und es gibt keine numerisch bewertbaren Methoden und keine Analysefehler
    Und leere Auswertungen sind ausdrücklich erlaubt
    Wenn das aktivierte Gate ausgewertet wird
    Dann scheitert es nicht allein wegen der leeren Auswertung
    Und der Report bezeichnet das Modul weiterhin als unbewertet

  @MC-025
  Szenario: Nach einer Grenzwertverletzung bleiben Reports verfügbar
    Angenommen PIT und alle aktivierten Reporter sind regulär abgeschlossen
    Und ein Methodenwert verletzt den aktivierten Grenzwert
    Wenn der nachgeschaltete Gate-Task ausgeführt wird
    Dann schlägt der Build fehl
    Und der JSON-Report bleibt vollständig und schemakonform lesbar
    Und der Markdown-Report und die normalen PIT-Reports bleiben verfügbar

  @MC-026
  Szenario: Maschinenlesbare Werte sind eindeutig und nachrechenbar
    Angenommen eine Auswertung enthält bewertete und unbewertete Methoden
    Wenn der JSON-Report geschrieben wird
    Dann enthält er Metrikversion, Policykennung, Toolversionen und Analysescope
    Und er enthält die verwendeten Methodenidentitäten und Status-Rohzähler
    Und nicht definierte numerische Werte werden als null geschrieben
    Und kein Feld enthält NaN oder Infinity
    Und der Report entspricht seinem versionierten JSON-Schema

9.6 06-gradle-integration.feature

# language: de
Funktionalität: Das PIT-Plugin in Gradle-Projekten einsetzen
  Als Java-Entwickler möchte ich meine vorhandene PIT-Konfiguration weiterverwenden,
  statt Mutationstests für die neue Metrik separat orchestrieren zu müssen.

  @MC-027
  Szenario: Unit- und Integrationstests bleiben über PIT konfigurierbar
    Angenommen ein Java-Projekt verwendet JUnit Jupiter
    Und seine PIT-Konfiguration berücksichtigt Unit- und Integrationstest-Source-Sets
    Und das Mutation-CRAP-Plugin ist aktiviert
    Wenn der dokumentierte Mutation-CRAP-Workflow gestartet wird
    Dann wird genau eine PIT-Analyse für diese Konfiguration angefordert
    Und deren Ergebnisse einschließlich der einbezogenen Integrationstests werden verwendet
    Und das Plugin startet keine zusätzlichen Gradle-Testtasks zur Komplexitätsermittlung

  @MC-028
  Szenario: Gleiche Methodenidentitäten aus zwei Teilprojekten bleiben getrennt
    Angenommen zwei Gradle-Teilprojekte enthalten jeweils dieselbe JVM-Klasse und Methodensignatur
    Und die beiden Methoden haben unterschiedliche Auswertungsergebnisse
    Wenn beide Teilprojekte parallel ihre PIT-Analyse durchführen
    Dann erhält jedes Teilprojekt seinen eigenen Report
    Und die Einträge tragen unterscheidbare Analysekontexte
    Und keine Ergebnisse oder Dateien werden gegenseitig überschrieben

  @MC-029
  Szenariogrundriss: Ein Anwender konsumiert das tatsächlich gepackte Plugin
    Angenommen das Plugin wurde in ein isoliertes lokales Test-Artefakt-Repository publiziert
    Und ein separates Anwenderprojekt verwendet <dsl>
    Und es kennt keine Klassenverzeichnisse aus dem Plugin-Entwicklungsbuild
    Wenn der dokumentierte Gradle/PIT-Aufruf gestartet wird
    Dann wird die Service-Factory aus dem Plugin-JAR gefunden
    Und alle erforderlichen Runtime-Abhängigkeiten werden geladen
    Und ein Report mit echten Java-Methodenwerten entsteht

    Beispiele:
      | dsl        |
      | Kotlin DSL |
      | Groovy DSL |

  @MC-030
  Szenario: Ein geänderter Grenzwert benötigt keine erneuten Mutationstests
    Angenommen ein für unveränderte Analyseinputs gültiger PIT-Report liegt vor
    Und die zugehörige PIT-Task kann korrekt als aktuell behandelt werden
    Wenn nur der Gate-Grenzwert geändert und der Gate-Task ausgeführt wird
    Dann wird die Gate-Entscheidung mit dem neuen Grenzwert neu berechnet
    Und dafür wird keine zusätzliche PIT-Analyse gestartet

  @MC-031
  Szenario: Ein gescheiterter PIT-Lauf darf keinen alten Report als Erfolg verwenden
    Angenommen ein erfolgreicher Report aus einem früheren Lauf existiert
    Und die aktuelle PIT-Analyse scheitert vor dem regulären Abschluss
    Wenn der dokumentierte CI-Workflow ausgeführt wird
    Dann bleibt der Build fehlgeschlagen
    Und es wird kein aktueller Gate-Erfolg aus dem alten Report abgeleitet

9.7 07-kotlin-jvm.feature

# language: de
@optional-kotlin
Funktionalität: Kotlin-JVM-Methoden ohne falsche Quelltextzusammenfassung auswerten
  Als Kotlin-Nutzer möchte ich erkennen, welche tatsächlich erzeugten JVM-Methoden
  mit der dokumentierten Compiler- und PIT-Kombination ausgewertet werden können.

  @MC-032
  Szenario: Gewöhnliche Kotlin-Methoden mit Verzweigungen auswerten
    Angenommen ein Kotlin-JVM-Fixture enthält gewöhnliche überladene Methoden mit Verzweigungen
    Und die gewählte PIT-Konfiguration erzeugt dafür auswertbare Mutanten
    Wenn das Plugin die zugehörigen Originalklassen mit JaCoCo analysiert
    Dann werden die Ergebnisse anhand von JVM-Klasse, Methodenname und Descriptor verbunden
    Und die Überladungen erhalten getrennte nachvollziehbare Auswertungen
    Und die verwendeten Kotlin-, PIT- und JaCoCo-Versionen sind dokumentiert

  @MC-033
  Szenario: Generierte Methoden nicht aufgrund gemeinsamer Quelltextzeilen zusammenfassen
    Angenommen eine Kotlin-Quelltextfunktion erzeugt mehrere JVM-Methoden
    Wenn das Plugin deren Komplexität und Mutationsergebnisse auswertet
    Dann werden JVM-Methoden nicht allein aufgrund gemeinsamer Quelltextzeilen vereinigt
    Und jede bewertete Methode hat eine nachweisbar passende Komplexität
    Und nicht auswertbare oder ausdrücklich gefilterte Fälle werden mit Gründen gekennzeichnet

10. Quellen und Prüfhinweise

Recherchestand: 1. Oktober 2026. Dies sind Primärquellen zu den verwendeten Erweiterungspunkten und Werkzeugen, keine Belege für die wissenschaftliche Validierung der neu definierten Metrik. Definitionen und Defaultentscheidungen in diesem Auftrag sind dort nicht automatisch vorgeschrieben.

Die Quellen wurden zur Spezifikation herangezogen; der beschriebene Plugin-Prototyp wurde im Rahmen der Dokumenterstellung nicht implementiert oder ausgeführt. Der Machbarkeitsnachweis bleibt deshalb ein ausdrücklicher erster Arbeitsauftrag.

[S1] PIT — Advanced Usage / Plugins / Mutation Result Listener. Service-Registrierung, Ausgabeformate, Tool-/Client-Classpath und Hinweise auf mögliche API-Änderungen.
https://pitest.org/quickstart/advanced/

[S2] PIT — MutationResultListenerFactory. Factory-Vertrag im untersuchten Entwicklungsstand. Am ausgewählten Release verifizieren.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultListenerFactory.java

[S3] PIT — Listener und klassenweise Ergebnisblöcke. Insbesondere der ausdrückliche Hinweis, dass Ergebnisse derselben Klasse auf mehrere Instanzen verteilt sein können.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultListener.java
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/ClassMutationResults.java

[S4] PIT — ListenerArguments und SourceLocator. Verfügbare Listenerargumente sowie Quelltextzugriff, der nicht mit Bytecodezugriff verwechselt werden darf.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/ListenerArguments.java
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/SourceLocator.java

[S5] PIT — ReportOptions. Kandidat für Classpath-/Codepath-Informationen; Nutzbarkeit und Kopplung an der gepinnten Version prüfen.
https://github.com/hcoles/pitest/blob/master/pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java

[S6] JaCoCo — Analyzer-API und Implementierung. Statische Analyse von Class-Dateien; die Implementierung behandelt fehlende Ausführungsdaten.
https://www.jacoco.org/jacoco/trunk/doc/api/org/jacoco/core/analysis/Analyzer.html
https://www.jacoco.org/jacoco/trunk/coverage/org.jacoco.core/org.jacoco.core.analysis/Analyzer.java.html

[S7] JaCoCo — IMethodCoverage und ICounter. JVM-Descriptor, Complexity-Counter und Gesamtanzahl.
https://www.jacoco.org/jacoco/trunk/doc/api/org/jacoco/core/analysis/IMethodCoverage.html
https://www.jacoco.org/jacoco/trunk/doc/api/org/jacoco/core/analysis/ICounter.html

[S8] JaCoCo — Coverage Counters. Zyklomatische Komplexität, fehlende Debuginformationen und Behandlung von Exception-Handling.
https://www.jacoco.org/jacoco/trunk/doc/counters.html

[S9] Gradle-PIT-Plugin — offizielle Projektdokumentation. Generische PIT-Plugins, Source-Sets, JUnit und Multi-Project-Builds. Versionsbeispiele der Webseite nicht ungeprüft übernehmen.
https://gradle-pitest-plugin.solidsoft.info/

[S10] PIT — Location. Klassenname, Methodenname und JVM-Methodendeskriptor.
https://github.com/hcoles/pitest/blob/master/pitest/src/main/java/org/pitest/mutationtest/engine/Location.java

[S11] PIT — DetectionStatus. Tatsächliche Statuswerte und deren isDetected()-Semantik. Am ausgewählten Release prüfen; nicht jede ältere Version enthält dieselben Enum-Werte.
https://github.com/hcoles/pitest/blob/master/pitest/src/main/java/org/pitest/mutationtest/DetectionStatus.java

[S12] JaCoCo — Change History. Insbesondere Änderungen an Filtern für Kotlin- und compilererzeugten Bytecode.
https://www.jacoco.org/jacoco/trunk/doc/changes.html

[S13] PIT — Basic Concepts. Bedeutung von Mutanten-Ergebnissen und nicht abgedeckten Mutanten.
https://pitest.org/quickstart/basic_concepts/

[S14] PIT — Projektwebseite. Abgrenzung zur erweiterten Sprach-/Framework-Unterstützung durch Arcmutate.
https://pitest.org/

[S15] Cucumber — Gherkin Reference. Features, Szenarien, Szenariogrundrisse, Beispiele, Tags und Sprachdeklaration.
https://cucumber.io/docs/gherkin/reference/

[S16] Gradle — Testing Build Logic with TestKit. Funktionstests für Gradle-Buildlogik mit separaten Anwenderprojekten.
https://docs.gradle.org/current/userguide/test_kit.html