Introduction
Test Coverage Visualizer is a comprehensive tool that analyzes and visualizes test coverage metrics across Java projects. It provides detailed insights into code coverage, generates interactive reports, and helps identify untested code paths.
Architecture Overview
Core Components
public class CoverageVisualizerArchitecture {
// Core analyzer engine
// Report generators (HTML, JSON, XML)
// Visualization components (charts, graphs)
// Data models for coverage metrics
// Plugin system for extensibility
}
Core Data Models
Coverage Metrics
public class CoverageMetrics {
private final String packageName;
private final String className;
private final String methodName;
private final int totalLines;
private final int coveredLines;
private final int totalBranches;
private final int coveredBranches;
private final int totalMethods;
private final int coveredMethods;
private final List<LineCoverage> lineCoverage;
private final double lineCoveragePercentage;
private final double branchCoveragePercentage;
private final double methodCoveragePercentage;
private final double overallCoverage;
// Constructors, getters, and calculation methods
public CoverageMetrics(String packageName, String className,
int totalLines, int coveredLines,
int totalBranches, int coveredBranches,
int totalMethods, int coveredMethods) {
this.packageName = packageName;
this.className = className;
this.totalLines = totalLines;
this.coveredLines = coveredLines;
this.totalBranches = totalBranches;
this.coveredBranches = coveredBranches;
this.totalMethods = totalMethods;
this.coveredMethods = coveredMethods;
this.lineCoveragePercentage = calculatePercentage(coveredLines, totalLines);
this.branchCoveragePercentage = calculatePercentage(coveredBranches, totalBranches);
this.methodCoveragePercentage = calculatePercentage(coveredMethods, totalMethods);
this.overallCoverage = calculateOverallCoverage();
}
private double calculatePercentage(int covered, int total) {
return total == 0 ? 100.0 : (double) covered / total * 100;
}
private double calculateOverallCoverage() {
return (lineCoveragePercentage + branchCoveragePercentage + methodCoveragePercentage) / 3;
}
}
public class LineCoverage {
private final int lineNumber;
private final String sourceCode;
private final CoverageStatus status;
private final int hitCount;
private final List<String> testMethods;
public LineCoverage(int lineNumber, String sourceCode, CoverageStatus status,
int hitCount, List<String> testMethods) {
this.lineNumber = lineNumber;
this.sourceCode = sourceCode;
this.status = status;
this.hitCount = hitCount;
this.testMethods = testMethods;
}
// Getters
public int getLineNumber() { return lineNumber; }
public String getSourceCode() { return sourceCode; }
public CoverageStatus getStatus() { return status; }
public int getHitCount() { return hitCount; }
public List<String> getTestMethods() { return testMethods; }
}
public enum CoverageStatus {
COVERED("covered", "green"),
PARTIALLY_COVERED("partially-covered", "yellow"),
NOT_COVERED("not-covered", "red"),
UNREACHABLE("unreachable", "gray");
private final String label;
private final String color;
CoverageStatus(String label, String color) {
this.label = label;
this.color = color;
}
public String getLabel() { return label; }
public String getColor() { return color; }
}
Project Structure
public class ProjectCoverage {
private final String projectName;
private final String baseDirectory;
private final Instant analysisTimestamp;
private final List<PackageCoverage> packages;
private final CoverageMetrics overallMetrics;
private final Map<String, TestCoverage> testCoverage;
public ProjectCoverage(String projectName, String baseDirectory) {
this.projectName = projectName;
this.baseDirectory = baseDirectory;
this.analysisTimestamp = Instant.now();
this.packages = new ArrayList<>();
this.testCoverage = new HashMap<>();
this.overallMetrics = calculateOverallMetrics();
}
public void addPackage(PackageCoverage packageCoverage) {
packages.add(packageCoverage);
}
public void addTestCoverage(String testClass, TestCoverage coverage) {
testCoverage.put(testClass, coverage);
}
private CoverageMetrics calculateOverallMetrics() {
int totalLines = packages.stream().mapToInt(p -> p.getMetrics().getTotalLines()).sum();
int coveredLines = packages.stream().mapToInt(p -> p.getMetrics().getCoveredLines()).sum();
int totalBranches = packages.stream().mapToInt(p -> p.getMetrics().getTotalBranches()).sum();
int coveredBranches = packages.stream().mapToInt(p -> p.getMetrics().getCoveredBranches()).sum();
int totalMethods = packages.stream().mapToInt(p -> p.getMetrics().getTotalMethods()).sum();
int coveredMethods = packages.stream().mapToInt(p -> p.getMetrics().getCoveredMethods()).sum();
return new CoverageMetrics("Overall", projectName, totalLines, coveredLines,
totalBranches, coveredBranches, totalMethods, coveredMethods);
}
// Getters and utility methods
public List<PackageCoverage> getPackages() { return packages; }
public CoverageMetrics getOverallMetrics() { return overallMetrics; }
public Map<String, TestCoverage> getTestCoverage() { return testCoverage; }
}
public class PackageCoverage {
private final String packageName;
private final List<ClassCoverage> classes;
private final CoverageMetrics metrics;
public PackageCoverage(String packageName) {
this.packageName = packageName;
this.classes = new ArrayList<>();
this.metrics = calculatePackageMetrics();
}
public void addClass(ClassCoverage classCoverage) {
classes.add(classCoverage);
}
private CoverageMetrics calculatePackageMetrics() {
int totalLines = classes.stream().mapToInt(c -> c.getMetrics().getTotalLines()).sum();
int coveredLines = classes.stream().mapToInt(c -> c.getMetrics().getCoveredLines()).sum();
int totalBranches = classes.stream().mapToInt(c -> c.getMetrics().getTotalBranches()).sum();
int coveredBranches = classes.stream().mapToInt(c -> c.getMetrics().getCoveredBranches()).sum();
int totalMethods = classes.stream().mapToInt(c -> c.getMetrics().getTotalMethods()).sum();
int coveredMethods = classes.stream().mapToInt(c -> c.getMetrics().getCoveredMethods()).sum();
return new CoverageMetrics(packageName, "Package", totalLines, coveredLines,
totalBranches, coveredBranches, totalMethods, coveredMethods);
}
}
public class ClassCoverage {
private final String className;
private final String sourceFile;
private final List<MethodCoverage> methods;
private final List<LineCoverage> lineCoverage;
private final CoverageMetrics metrics;
// Constructor and methods similar to PackageCoverage
}
Coverage Analysis Engine
Core Analyzer
public class CoverageAnalyzer {
private final CoverageParser coverageParser;
private final SourceCodeReader sourceReader;
private final CoverageCalculator calculator;
private final ReportGenerator reportGenerator;
public CoverageAnalyzer() {
this.coverageParser = new JaCoCoParser();
this.sourceReader = new SourceCodeReader();
this.calculator = new CoverageCalculator();
this.reportGenerator = new HTMLReportGenerator();
}
public ProjectCoverage analyzeProject(String projectPath, String coverageDataPath) {
ProjectCoverage projectCoverage = new ProjectCoverage(
extractProjectName(projectPath), projectPath);
// Parse coverage data
CoverageData coverageData = coverageParser.parse(coverageDataPath);
// Analyze source code structure
analyzeSourceStructure(projectPath, projectCoverage);
// Calculate coverage metrics
calculateCoverageMetrics(projectCoverage, coverageData);
// Generate visualization data
generateVisualizationData(projectCoverage);
return projectCoverage;
}
private void analyzeSourceStructure(String projectPath, ProjectCoverage projectCoverage) {
try {
Files.walk(Paths.get(projectPath))
.filter(path -> path.toString().endsWith(".java"))
.forEach(sourceFile -> {
try {
ClassCoverage classCoverage = analyzeSourceFile(sourceFile);
addToProjectStructure(projectCoverage, classCoverage);
} catch (IOException e) {
System.err.println("Error analyzing source file: " + sourceFile);
}
});
} catch (IOException e) {
throw new CoverageAnalysisException("Error walking project directory", e);
}
}
private ClassCoverage analyzeSourceFile(Path sourceFile) throws IOException {
String className = extractClassName(sourceFile);
List<String> sourceLines = Files.readAllLines(sourceFile);
ClassCoverage classCoverage = new ClassCoverage(className, sourceFile.toString());
// Analyze methods and lines
analyzeMethodsAndLines(sourceLines, classCoverage);
return classCoverage;
}
private void analyzeMethodsAndLines(List<String> sourceLines, ClassCoverage classCoverage) {
// Implementation to parse methods and identify code lines
// This would use JavaParser or similar library
}
private void calculateCoverageMetrics(ProjectCoverage projectCoverage, CoverageData coverageData) {
projectCoverage.getPackages().forEach(pkg ->
pkg.getClasses().forEach(cls ->
calculator.calculateClassCoverage(cls, coverageData)
)
);
}
private void generateVisualizationData(ProjectCoverage projectCoverage) {
// Generate data structures for visualization
generateCoverageTree(projectCoverage);
generateHeatMapData(projectCoverage);
generateTrendData(projectCoverage);
}
}
public class CoverageCalculator {
public void calculateClassCoverage(ClassCoverage classCoverage, CoverageData coverageData) {
String className = classCoverage.getClassName();
// Calculate line coverage
calculateLineCoverage(classCoverage, coverageData.getLineCoverage(className));
// Calculate branch coverage
calculateBranchCoverage(classCoverage, coverageData.getBranchCoverage(className));
// Calculate method coverage
calculateMethodCoverage(classCoverage, coverageData.getMethodCoverage(className));
}
private void calculateLineCoverage(ClassCoverage classCoverage, Map<Integer, Integer> lineHits) {
classCoverage.getLineCoverage().forEach(line -> {
Integer hitCount = lineHits.get(line.getLineNumber());
if (hitCount != null && hitCount > 0) {
line.setStatus(hitCount > 0 ? CoverageStatus.COVERED : CoverageStatus.NOT_COVERED);
line.setHitCount(hitCount);
}
});
}
// Similar methods for branch and method coverage
}
Parser Implementations
JaCoCo Parser
public class JaCoCoParser implements CoverageParser {
@Override
public CoverageData parse(String coverageFilePath) {
try {
File coverageFile = new File(coverageFilePath);
if (!coverageFile.exists()) {
throw new CoverageFileNotFoundException("Coverage file not found: " + coverageFilePath);
}
if (coverageFilePath.endsWith(".exec")) {
return parseExecFile(coverageFile);
} else if (coverageFilePath.endsWith(".xml")) {
return parseXmlFile(coverageFile);
} else {
throw new UnsupportedCoverageFormatException("Unsupported coverage format: " + coverageFilePath);
}
} catch (Exception e) {
throw new CoverageParseException("Error parsing coverage file", e);
}
}
private CoverageData parseExecFile(File execFile) {
CoverageData coverageData = new CoverageData();
try (FileInputStream fis = new FileInputStream(execFile)) {
ExecutionDataReader reader = new ExecutionDataReader(fis);
reader.setSessionInfoVisitor(new SessionInfoVisitor(coverageData));
reader.setExecutionDataVisitor(new ExecutionDataVisitor(coverageData));
reader.read();
} catch (IOException e) {
throw new CoverageParseException("Error reading .exec file", e);
}
return coverageData;
}
private CoverageData parseXmlFile(File xmlFile) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(xmlFile);
return parseXmlDocument(document);
} catch (Exception e) {
throw new CoverageParseException("Error parsing XML coverage file", e);
}
}
private CoverageData parseXmlDocument(Document document) {
CoverageData coverageData = new CoverageData();
NodeList packageNodes = document.getElementsByTagName("package");
for (int i = 0; i < packageNodes.getLength(); i++) {
Element packageElement = (Element) packageNodes.item(i);
parsePackageElement(packageElement, coverageData);
}
return coverageData;
}
private void parsePackageElement(Element packageElement, CoverageData coverageData) {
String packageName = packageElement.getAttribute("name");
NodeList classNodes = packageElement.getElementsByTagName("class");
for (int i = 0; i < classNodes.getLength(); i++) {
Element classElement = (Element) classNodes.item(i);
parseClassElement(classElement, packageName, coverageData);
}
}
private void parseClassElement(Element classElement, String packageName, CoverageData coverageData) {
String className = classElement.getAttribute("name");
String fullClassName = packageName + "." + className;
// Parse line coverage
parseLineCoverage(classElement, fullClassName, coverageData);
// Parse method coverage
parseMethodCoverage(classElement, fullClassName, coverageData);
// Parse branch coverage
parseBranchCoverage(classElement, fullClassName, coverageData);
}
// Implementation details for parsing specific coverage data
}
// Visitor classes for JaCoCo .exec format
class SessionInfoVisitor implements ISessionInfoVisitor {
private final CoverageData coverageData;
public SessionInfoVisitor(CoverageData coverageData) {
this.coverageData = coverageData;
}
@Override
public void visitSessionInfo(SessionInfo info) {
coverageData.setSessionStart(info.getStartTimeStamp());
coverageData.setSessionDump(info.getDumpTimeStamp());
}
}
class ExecutionDataVisitor implements IExecutionDataVisitor {
private final CoverageData coverageData;
public ExecutionDataVisitor(CoverageData coverageData) {
this.coverageData = coverageData;
}
@Override
public void visitClassExecution(ExecutionData data) {
coverageData.addClassExecutionData(data.getName(), data);
}
}
Visualization Components
HTML Report Generator
public class HTMLReportGenerator implements ReportGenerator {
private final TemplateEngine templateEngine;
private final String outputDirectory;
public HTMLReportGenerator() {
this.templateEngine = createTemplateEngine();
this.outputDirectory = "coverage-reports";
}
@Override
public void generateReport(ProjectCoverage projectCoverage) {
createOutputDirectory();
// Generate main index page
generateIndexPage(projectCoverage);
// Generate package-level reports
generatePackageReports(projectCoverage);
// Generate class-level reports
generateClassReports(projectCoverage);
// Generate overview dashboard
generateDashboard(projectCoverage);
// Generate static resources (CSS, JS)
generateStaticResources();
}
private void generateIndexPage(ProjectCoverage projectCoverage) {
Map<String, Object> model = new HashMap<>();
model.put("project", projectCoverage);
model.put("timestamp", Instant.now());
model.put("overallMetrics", projectCoverage.getOverallMetrics());
String html = templateEngine.process("index", model);
writeToFile("index.html", html);
}
private void generatePackageReports(ProjectCoverage projectCoverage) {
projectCoverage.getPackages().forEach(pkg -> {
Map<String, Object> model = new HashMap<>();
model.put("package", pkg);
model.put("project", projectCoverage);
String html = templateEngine.process("package", model);
writeToFile("packages/" + pkg.getPackageName() + ".html", html);
});
}
private void generateClassReports(ProjectCoverage projectCoverage) {
projectCoverage.getPackages().forEach(pkg -> {
pkg.getClasses().forEach(cls -> {
Map<String, Object> model = new HashMap<>();
model.put("class", cls);
model.put("package", pkg);
model.put("project", projectCoverage);
String html = templateEngine.process("class", model);
writeToFile("classes/" + cls.getClassName() + ".html", html);
});
});
}
private void generateDashboard(ProjectCoverage projectCoverage) {
Map<String, Object> model = new HashMap<>();
model.put("project", projectCoverage);
model.put("coverageData", prepareDashboardData(projectCoverage));
String html = templateEngine.process("dashboard", model);
writeToFile("dashboard.html", html);
}
private Map<String, Object> prepareDashboardData(ProjectCoverage projectCoverage) {
Map<String, Object> data = new HashMap<>();
// Prepare data for charts
data.put("lineCoverageByPackage", getLineCoverageByPackage(projectCoverage));
data.put("methodCoverageByPackage", getMethodCoverageByPackage(projectCoverage));
data.put("branchCoverageByPackage", getBranchCoverageByPackage(projectCoverage));
data.put("coverageTrend", getCoverageTrend(projectCoverage));
data.put("topUncoveredMethods", getTopUncoveredMethods(projectCoverage));
return data;
}
private void generateStaticResources() {
// Copy CSS, JavaScript, and other static files
copyResource("static/style.css", "static/style.css");
copyResource("static/charts.js", "static/charts.js");
copyResource("static/coverage.js", "static/coverage.js");
}
private TemplateEngine createTemplateEngine() {
TemplateEngine engine = new TemplateEngine();
// Configure template resolver
return engine;
}
private void writeToFile(String filename, String content) {
try {
Path outputPath = Paths.get(outputDirectory, filename);
Files.createDirectories(outputPath.getParent());
Files.write(outputPath, content.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new ReportGenerationException("Error writing report file: " + filename, e);
}
}
}
Chart Data Generators
public class ChartDataGenerator {
public ChartData generatePackageCoverageChart(ProjectCoverage projectCoverage) {
List<ChartDataPoint> dataPoints = projectCoverage.getPackages().stream()
.map(pkg -> new ChartDataPoint(
pkg.getPackageName(),
pkg.getMetrics().getOverallCoverage(),
pkg.getMetrics().getLineCoveragePercentage(),
pkg.getMetrics().getBranchCoveragePercentage(),
pkg.getMetrics().getMethodCoveragePercentage()
))
.sorted(Comparator.comparing(ChartDataPoint::getValue).reversed())
.collect(Collectors.toList());
return new ChartData("Package Coverage", dataPoints, "horizontalBar");
}
public ChartData generateCoverageTrendChart(List<ProjectCoverage> historicalData) {
List<ChartDataPoint> dataPoints = historicalData.stream()
.map(project -> new ChartDataPoint(
project.getAnalysisTimestamp().toString(),
project.getOverallMetrics().getOverallCoverage()
))
.collect(Collectors.toList());
return new ChartData("Coverage Trend Over Time", dataPoints, "line");
}
public ChartData generateCoverageDistributionChart(ProjectCoverage projectCoverage) {
Map<String, Long> distribution = projectCoverage.getPackages().stream()
.collect(Collectors.groupingBy(
pkg -> getCoverageRange(pkg.getMetrics().getOverallCoverage()),
Collectors.counting()
));
List<ChartDataPoint> dataPoints = distribution.entrySet().stream()
.map(entry -> new ChartDataPoint(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
return new ChartData("Coverage Distribution", dataPoints, "pie");
}
private String getCoverageRange(double coverage) {
if (coverage >= 90) return "90-100%";
if (coverage >= 80) return "80-89%";
if (coverage >= 70) return "70-79%";
if (coverage >= 60) return "60-69%";
return "0-59%";
}
}
public class ChartData {
private final String title;
private final List<ChartDataPoint> dataPoints;
private final String chartType;
private final Map<String, Object> options;
public ChartData(String title, List<ChartDataPoint> dataPoints, String chartType) {
this.title = title;
this.dataPoints = dataPoints;
this.chartType = chartType;
this.options = createDefaultOptions();
}
private Map<String, Object> createDefaultOptions() {
Map<String, Object> options = new HashMap<>();
options.put("responsive", true);
options.put("maintainAspectRatio", false);
return options;
}
// Getters
public String getTitle() { return title; }
public List<ChartDataPoint> getDataPoints() { return dataPoints; }
public String getChartType() { return chartType; }
public Map<String, Object> getOptions() { return options; }
}
public class ChartDataPoint {
private final String label;
private final double value;
private final double lineCoverage;
private final double branchCoverage;
private final double methodCoverage;
public ChartDataPoint(String label, double value) {
this(label, value, value, value, value);
}
public ChartDataPoint(String label, double value, double lineCoverage,
double branchCoverage, double methodCoverage) {
this.label = label;
this.value = value;
this.lineCoverage = lineCoverage;
this.branchCoverage = branchCoverage;
this.methodCoverage = methodCoverage;
}
// Getters
public String getLabel() { return label; }
public double getValue() { return value; }
public double getLineCoverage() { return lineCoverage; }
public double getBranchCoverage() { return branchCoverage; }
public double getMethodCoverage() { return methodCoverage; }
}
Interactive Features
Source Code Viewer
public class SourceCodeViewer {
public String generateAnnotatedSource(ClassCoverage classCoverage) {
StringBuilder html = new StringBuilder();
html.append("<div class=\"source-code\">");
html.append("<div class=\"source-header\">").append(classCoverage.getClassName()).append("</div>");
List<LineCoverage> lines = classCoverage.getLineCoverage();
for (int i = 0; i < lines.size(); i++) {
LineCoverage line = lines.get(i);
html.append(generateSourceLine(i + 1, line));
}
html.append("</div>");
return html.toString();
}
private String generateSourceLine(int lineNumber, LineCoverage line) {
String statusClass = line.getStatus().getLabel();
String color = line.getStatus().getColor();
return String.format(
"<div class=\"source-line %s\" data-line=\"%d\" data-hits=\"%d\">" +
"<span class=\"line-number\">%d</span>" +
"<span class=\"line-content\">%s</span>" +
"<span class=\"coverage-info\">%s</span>" +
"</div>",
statusClass, lineNumber, line.getHitCount(), lineNumber,
escapeHtml(line.getSourceCode()), generateCoverageInfo(line)
);
}
private String generateCoverageInfo(LineCoverage line) {
if (line.getStatus() == CoverageStatus.COVERED) {
return String.format("✓ %d hits", line.getHitCount());
} else if (line.getStatus() == CoverageStatus.NOT_COVERED) {
return "✗ Not covered";
} else if (line.getStatus() == CoverageStatus.PARTIALLY_COVERED) {
return "~ Partially covered";
} else {
return "-";
}
}
private String escapeHtml(String text) {
return text.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
}
Coverage Heat Map
public class CoverageHeatMap {
public String generateHeatMap(ProjectCoverage projectCoverage) {
Map<String, Double> packageCoverage = projectCoverage.getPackages().stream()
.collect(Collectors.toMap(
PackageCoverage::getPackageName,
pkg -> pkg.getMetrics().getOverallCoverage()
));
return generateHeatMapSvg(packageCoverage);
}
private String generateHeatMapSvg(Map<String, Double> packageCoverage) {
StringBuilder svg = new StringBuilder();
svg.append("<svg width=\"800\" height=\"600\" xmlns=\"http://www.w3.org/2000/svg\">");
int x = 10, y = 10;
int rectSize = 20;
for (Map.Entry<String, Double> entry : packageCoverage.entrySet()) {
String color = getCoverageColor(entry.getValue());
svg.append(String.format(
"<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" " +
"data-package=\"%s\" data-coverage=\"%.1f\"/>",
x, y, rectSize, rectSize, color, entry.getKey(), entry.getValue()
));
x += rectSize + 2;
if (x > 780) {
x = 10;
y += rectSize + 2;
}
}
svg.append("</svg>");
return svg.toString();
}
private String getCoverageColor(double coverage) {
if (coverage >= 80) return "#4CAF50"; // Green
if (coverage >= 60) return "#FFC107"; // Yellow
if (coverage >= 40) return "#FF9800"; // Orange
return "#F44336"; // Red
}
}
Command Line Interface
Main Application
public class TestCoverageVisualizer {
public static void main(String[] args) {
CLIArguments cliArgs = parseArguments(args);
if (cliArgs.isHelp()) {
printHelp();
return;
}
try {
CoverageAnalyzer analyzer = new CoverageAnalyzer();
ProjectCoverage projectCoverage = analyzer.analyzeProject(
cliArgs.getProjectPath(),
cliArgs.getCoverageDataPath()
);
ReportGenerator reportGenerator = createReportGenerator(cliArgs.getFormat());
reportGenerator.generateReport(projectCoverage);
printSummary(projectCoverage);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
System.exit(1);
}
}
private static CLIArguments parseArguments(String[] args) {
CLIArguments arguments = new CLIArguments();
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "-p":
case "--project":
arguments.setProjectPath(args[++i]);
break;
case "-c":
case "--coverage":
arguments.setCoverageDataPath(args[++i]);
break;
case "-f":
case "--format":
arguments.setFormat(args[++i]);
break;
case "-o":
case "--output":
arguments.setOutputDirectory(args[++i]);
break;
case "-h":
case "--help":
arguments.setHelp(true);
break;
}
}
return arguments;
}
private static void printHelp() {
System.out.println("Test Coverage Visualizer");
System.out.println("Usage: java TestCoverageVisualizer [options]");
System.out.println();
System.out.println("Options:");
System.out.println(" -p, --project <path> Project source directory");
System.out.println(" -c, --coverage <path> Coverage data file (.exec or .xml)");
System.out.println(" -f, --format <format> Output format (html, json, xml)");
System.out.println(" -o, --output <dir> Output directory");
System.out.println(" -h, --help Show this help message");
}
private static void printSummary(ProjectCoverage projectCoverage) {
CoverageMetrics metrics = projectCoverage.getOverallMetrics();
System.out.println("Coverage Analysis Summary:");
System.out.printf(" Overall Coverage: %.1f%%\n", metrics.getOverallCoverage());
System.out.printf(" Line Coverage: %.1f%%\n", metrics.getLineCoveragePercentage());
System.out.printf(" Branch Coverage: %.1f%%\n", metrics.getBranchCoveragePercentage());
System.out.printf(" Method Coverage: %.1f%%\n", metrics.getMethodCoveragePercentage());
System.out.println();
System.out.println("Report generated in: coverage-reports/");
}
}
public class CLIArguments {
private String projectPath;
private String coverageDataPath;
private String format = "html";
private String outputDirectory = "coverage-reports";
private boolean help = false;
// Getters and setters
public String getProjectPath() { return projectPath; }
public void setProjectPath(String projectPath) { this.projectPath = projectPath; }
public String getCoverageDataPath() { return coverageDataPath; }
public void setCoverageDataPath(String coverageDataPath) { this.coverageDataPath = coverageDataPath; }
public String getFormat() { return format; }
public void setFormat(String format) { this.format = format; }
public String getOutputDirectory() { return outputDirectory; }
public void setOutputDirectory(String outputDirectory) { this.outputDirectory = outputDirectory; }
public boolean isHelp() { return help; }
public void setHelp(boolean help) { this.help = help; }
}
Configuration System
Flexible Configuration
@Configuration
public class VisualizerConfig {
@Value("${coverage.visualizer.output-dir:coverage-reports}")
private String outputDirectory;
@Value("${coverage.visualizer.format:html}")
private String reportFormat;
@Value("${coverage.visualizer.theme:light}")
private String theme;
@Value("${coverage.visualizer.charts.enabled:true}")
private boolean chartsEnabled;
@Value("${coverage.visualizer.heatmap.enabled:true}")
private boolean heatmapEnabled;
@Bean
public CoverageAnalyzer coverageAnalyzer() {
return new CoverageAnalyzer();
}
@Bean
public ReportGenerator reportGenerator() {
switch (reportFormat.toLowerCase()) {
case "html":
return new HTMLReportGenerator();
case "json":
return new JSONReportGenerator();
case "xml":
return new XMLReportGenerator();
default:
throw new IllegalArgumentException("Unsupported report format: " + reportFormat);
}
}
@Bean
public ChartDataGenerator chartDataGenerator() {
return new ChartDataGenerator();
}
}
public class VisualizerProperties {
private String outputDir = "coverage-reports";
private String format = "html";
private String theme = "light";
private Charts charts = new Charts();
private Heatmap heatmap = new Heatmap();
public static class Charts {
private boolean enabled = true;
private String type = "interactive";
private int maxPoints = 1000;
// Getters and setters
}
public static class Heatmap {
private boolean enabled = true;
private String colorScheme = "red-green";
private int resolution = 10;
// Getters and setters
}
// Getters and setters
}
Exception Handling
Custom Exceptions
public class CoverageAnalysisException extends RuntimeException {
public CoverageAnalysisException(String message) {
super(message);
}
public CoverageAnalysisException(String message, Throwable cause) {
super(message, cause);
}
}
public class CoverageFileNotFoundException extends CoverageAnalysisException {
public CoverageFileNotFoundException(String message) {
super(message);
}
}
public class UnsupportedCoverageFormatException extends CoverageAnalysisException {
public UnsupportedCoverageFormatException(String message) {
super(message);
}
}
public class CoverageParseException extends CoverageAnalysisException {
public CoverageParseException(String message, Throwable cause) {
super(message, cause);
}
}
public class ReportGenerationException extends CoverageAnalysisException {
public ReportGenerationException(String message, Throwable cause) {
super(message, cause);
}
}
This Test Coverage Visualizer provides a comprehensive solution for analyzing and visualizing test coverage data in Java projects. It supports multiple coverage formats, generates interactive HTML reports with charts and heat maps, and offers both command-line and programmatic interfaces for integration into build pipelines and development workflows.