
Dart Test Coverage
- 109 installs
- 139 repo stars
- Updated July 11, 2026
- kevmoo/dash_skills
Helps with testing & qa tasks.
About
dart-test-coverage is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.
- dart-test-coverage
- Testing & QA
- AI-coding skill
Dart Test Coverage by the numbers
- 109 all-time installs (skills.sh)
- +10 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #971 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/kevmoo/dash_skills --skill dart-test-coverageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 109 |
|---|---|
| repo stars | ★ 139 |
| Last updated | July 11, 2026 |
| Repository | kevmoo/dash_skills ↗ |
What it does
Helps with testing & qa tasks.
Files
Dart Test Coverage
Guidelines for running and interpreting test coverage in Dart packages.
When to use this skill
- When asked to "check test coverage" or "improve coverage".
- When you need to identify which parts of a library are untested.
Discovery
To find areas lacking test coverage:
Run Coverage Analysis
Follow the workflow to generate and interpret coverage data: 1. Run Tests with Coverage: dart test --coverage=.dart_tool/coverage 2. Interpret Results: Use the script or format_coverage as described in the Interpreting Results section to identify specific files and missed lines.
How to use this skill (The Workflow)
1. Ensure tests pass by running dart test. 2. Collect coverage by running dart test --coverage=.dart_tool/coverage. 3. Interpret the results using the provided script or standard tools. 4. Add tests to cover missed lines.
Running Coverage
Run the following command to collect coverage in JSON format:
dart test --coverage=.dart_tool/coverage[!NOTE]
We use.dart_tool/coverageas the output directory because.dart_tool
is typically already ignored in .gitignore files.[!TIP]
For projects with complex conditional logic, you can pass the
--branch-coverageflag todart testto collect branch-level coverage.
Interpreting Results
Option 1: Use the custom interpreter script
This repository includes a zero-dependency script that parses the raw JSON output and provides a summary of covered percentage and missed lines.
Run it from the project root (adjust path to script as needed):
dart run skills/dart-test-coverage/scripts/interpret_coverage.dart .dart_tool/coverage <package_name>Replace <package_name> with the name from pubspec.yaml.
Example Output:
package:my_pkg/src/file.dart: 50.0% (2/4 lines)
Missed lines: 3, 4Option 2: Use package:coverage
If package:test is installed, package:coverage is likely available as a transitive dependency. You can use its format_coverage tool.
To get a human-readable "pretty print" of the coverage:
dart run coverage:format_coverage --in=.dart_tool/coverage --out=stdout --pretty-print --report-on=libThis will output the file content with hit counts on the left (e.g., 0| for missed lines).
Best Practices for Reporting Results
When presenting coverage results to the user, follow these guidelines: 1. State the high-level percentage first to give immediate context. 2. Identify specific files and missed lines clearly. 3. Translate line numbers to code: Don't just say "lines 3-6 are missed". Look at the source file and tell the user which functions or blocks are untested (e.g., "The divide function is missing coverage"). 4. Propose concrete fixes: Provide example test code that the user can immediately apply to cover the missed lines. 5. Use tables for multi-file summaries: When reporting on multiple files, use a markdown table with columns for File, Coverage %, and Missed Lines to make the summary easy to scan.
Constraints
- ALWAYS verify that tests pass before collecting coverage.
- DO NOT commit the
.dart_tool/coveragedirectory. - Focus coverage improvements on
lib/files, nottest/or generated files.
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;
int divide(int a, int b) {
if (b == 0) throw ArgumentError('Cannot divide by zero');
return a ~/ b;
}
name: dummy_pkg
publish_to: none
environment:
sdk: ^3.9.0
resolution: workspace
dev_dependencies:
test: ^1.24.0
import 'package:dummy_pkg/src/calculator.dart';
import 'package:test/test.dart';
void main() {
test('add', () {
expect(add(1, 2), 3);
});
test('subtract', () {
expect(subtract(2, 1), 1);
});
}
#!/usr/bin/env dart
import 'dart:convert';
import 'dart:io';
void main(List<String> args) {
if (args.isEmpty) {
print(
'Usage: dart interpret_coverage.dart <coverage_directory> [package_name]',
);
exitCode = 64; // Bad usage
return;
}
final dirPath = args[0];
final packageName = args.length > 1 ? args[1] : null;
final dir = Directory(dirPath);
if (!dir.existsSync()) {
print('Directory not found: $dirPath');
exitCode = 78; // Configuration error
return;
}
final files = dir
.listSync(recursive: true)
.whereType<File>()
.where((f) => f.path.endsWith('.vm.json'));
final coverageData = processCoverage(files, packageName);
if (coverageData.isEmpty) {
print('No coverage data found for the specified criteria.');
return;
}
// Print summary
coverageData.forEach((source, lineHits) {
final totalLines = lineHits.length;
final coveredLines = lineHits.values.where((hits) => hits > 0).length;
final percent = totalLines > 0
? (coveredLines / totalLines * 100).toStringAsFixed(1)
: '100';
print('$source: $percent% ($coveredLines/$totalLines lines)');
// Show missed lines
final missed =
lineHits.entries.where((e) => e.value == 0).map((e) => e.key).toList()
..sort();
if (missed.isNotEmpty) {
print(' Missed lines: ${missed.join(', ')}');
}
});
}
/// Processes a list of coverage files and returns a map of file paths to line hit counts.
///
/// The returned map structure is: `{ file_uri: { line_number: hit_count } }`.
///
/// If [packageName] is provided, only files starting with `package:$packageName/` are included.
/// Files containing `/test/` are skipped.
Map<String, Map<int, int>> processCoverage(
Iterable<File> files,
String? packageName,
) {
final coverageData = <String, Map<int, int>>{}; // file -> (line -> hits)
for (final file in files) {
final content = file.readAsStringSync();
final json = jsonDecode(content) as Map<String, dynamic>;
final coverage = json['coverage'] as List<dynamic>;
for (final item in coverage) {
final source = item['source'] as String;
// Filter by package name if provided
if (packageName != null && !source.startsWith('package:$packageName/')) {
continue;
}
// Skip test files usually
if (source.contains('/test/')) continue;
final hits = item['hits'] as List<dynamic>;
final fileData = coverageData.putIfAbsent(source, () => <int, int>{});
for (var i = 0; i < hits.length; i += 2) {
final line = hits[i] as int;
final count = hits[i + 1] as int;
fileData[line] = (fileData[line] ?? 0) + count;
}
}
}
return coverageData;
}
name: interpret_coverage
environment:
sdk: '^3.9.0'
resolution: workspace
dev_dependencies:
test: ^1.24.0
import 'dart:convert';
import 'dart:io';
import 'package:test/test.dart';
import '../interpret_coverage.dart';
void main() {
/// Helper to create a mock coverage file.
/// Takes a list of items where 'hits' is a Map<int, int> (line -> hits).
File createMockCoverageFile(
Directory dir,
String filename,
List<Map<String, dynamic>> coverageItems,
) {
final file = File('${dir.path}/$filename');
final processedItems = coverageItems.map((item) {
final source = item['source'] as String;
final hitsMap = item['hits'] as Map<int, int>;
final flatHits = <int>[];
hitsMap.forEach((line, count) {
flatHits.add(line);
flatHits.add(count);
});
return {'source': source, 'hits': flatHits};
}).toList();
final json = {'coverage': processedItems};
file.writeAsStringSync(jsonEncode(json));
return file;
}
test('processCoverage extracts data correctly', () {
final tempDir = Directory.systemTemp.createTempSync('coverage_test');
final coverageFile = createMockCoverageFile(tempDir, 'test.vm.json', [
{
'source': 'package:foo/bar.dart',
'hits': {1: 1, 2: 0},
},
]);
try {
final result = processCoverage([coverageFile], null);
expect(result, contains('package:foo/bar.dart'));
expect(result['package:foo/bar.dart'], {1: 1, 2: 0});
} finally {
tempDir.deleteSync(recursive: true);
}
});
test('processCoverage filters by package name', () {
final tempDir = Directory.systemTemp.createTempSync('coverage_test');
final coverageFile = createMockCoverageFile(tempDir, 'test.vm.json', [
{
'source': 'package:foo/bar.dart',
'hits': {1: 1},
},
{
'source': 'package:baz/qux.dart',
'hits': {1: 1},
},
]);
try {
final result = processCoverage([coverageFile], 'foo');
expect(result, contains('package:foo/bar.dart'));
expect(result, isNot(contains('package:baz/qux.dart')));
} finally {
tempDir.deleteSync(recursive: true);
}
});
test('processCoverage skips test files', () {
final tempDir = Directory.systemTemp.createTempSync('coverage_test');
final coverageFile = createMockCoverageFile(tempDir, 'test.vm.json', [
{
'source': 'package:foo/test/bar_test.dart',
'hits': {1: 1},
},
{
'source': 'package:foo/bar.dart',
'hits': {1: 1},
},
]);
try {
final result = processCoverage([coverageFile], 'foo');
expect(result, contains('package:foo/bar.dart'));
expect(result, isNot(contains('package:foo/test/bar_test.dart')));
} finally {
tempDir.deleteSync(recursive: true);
}
});
}