Test Coverage with covr
The covr package is the standard tool for measuring test code coverage in R. It tracks which lines of your package are executed during tests and helps you identify code that lacks test coverage. This guide covers everything you need to start using covr effectively in your package development workflow.
Installation
You can install covr from CRAN or use the development version from GitHub:
# Stable CRAN version
install.packages("covr")
# Development version from GitHub
devtools::install_github("r-lib/covr")
The package is maintained by the r-lib team and works with any testing framework including testthat, RUnit, or custom test setups.
Calculating Package Coverage
The main function you’ll use is package_coverage(). Run it from your package root directory:
library(covr)
# Calculate coverage for your package tests
cov <- package_coverage()
# View the coverage object
cov
# Coverage: 85.71%
# - R/add.R: 80.00%
# - R/subtract.R: 100.00%
The function returns a coverage object showing which files have what percentage of lines covered by your tests. By default, it runs your package tests located in tests/testthat/.
Coverage Types
You can measure different types of coverage depending on what you want to analyze:
# Coverage from tests (default)
cov_tests <- package_coverage(type = "tests")
# Coverage from vignette code
cov_vignettes <- package_coverage(type = "vignettes")
# Coverage from examples in documentation
cov_examples <- package_coverage(type = "examples")
# Coverage from all sources combined
cov_all <- package_coverage(type = "all")
This flexibility lets you check not just your test suite but also whether your examples and vignettes actually run without errors.
Understanding Coverage Reports
Interactive HTML Reports
The report() function generates an interactive HTML report that lets you browse through your code and see exactly which lines are covered:
cov <- package_coverage()
report(cov)
# Generates and opens coverage-report.html in your browser
This report shows your source code with green highlighting for covered lines and red for uncovered lines. It’s the most useful view for identifying exactly what needs more tests.
Finding Uncovered Lines
The zero_coverage() function returns only the uncovered lines as a data frame:
cov <- package_coverage()
uncovered <- zero_coverage(cov)
# View uncovered lines
print(uncovered)
# filename functions first_line last_line
# 1 R/add.R <anonymous> 10 15
# 2 R/add.R <anonymous> 20 22
This gives you a focused list of exactly which lines need test coverage.
Excluding Code from Coverage
Sometimes you need to exclude certain code from coverage calculations. For example, deprecated functions, debug code, or platform-specific helpers might not be worth testing in all contexts.
Using .covrignore File
Create a .covrignore file in your package root:
# Exclude entire directories
inst/
# Exclude specific files
R/deprecated.R
tests/
Using Comments in Source Code
Add # nocov comments directly in your R files:
# Exclude a single line
f1 <- function(x) {
x + 1 # nocov
}
# Exclude a block of code
f2 <- function(x) { # nocov start
message("Debug info")
x + 2
} # nocov end
For compiled code, use // # nocov instead.
Using Function Arguments
You can also exclude code programmatically:
# Exclude specific functions by pattern
cov <- package_coverage(
function_exclusions = c(
"^\\.helper", # Internal helper functions
"^print\\.", # Print methods
"^format\\." # Format methods
)
)
# Exclude specific line ranges in files
cov <- package_coverage(
line_exclusions = list(
"R/utils.R" = c(1:10, 15),
"R/deprecated.R"
)
)
Measuring Coverage for Individual Functions
For quick checks or non-package code, use function_coverage() to measure coverage of a single function:
add <- function(x, y) {
if (!is.numeric(x) || !is.numeric(y)) {
stop("Both arguments must be numeric")
}
x + y
}
# Test without covering the error condition
cov1 <- function_coverage(fun = add, code = add(1, 2))
# Returns: Coverage: 50%
# Test with full coverage
cov2 <- function_coverage(
fun = add,
code = {
add(1, 2) == 3
tryCatch(
add("a", 2),
error = function(e) TRUE
)
}
)
# Returns: Coverage: 100%
This is useful for testing utility functions outside a package context.
CI Integration
covr integrates with popular CI services to track coverage over time.
GitHub Actions
The easiest way to add coverage tracking is using usethis:
usethis::use_github_action("test-coverage")
This creates a GitHub Actions workflow that runs your tests with covr and uploads results to Codecov. Every push and pull request will show coverage changes.
Manual Codecov Integration
If you prefer manual setup:
library(covr)
# Generate coverage and submit to Codecov
cov <- package_coverage()
codecov(token = "your-codecov-token")
Coveralls Integration
Similarly for Coveralls:
coveralls(repo_token = "your-coveralls-token")
Common Issues and Solutions
Cannot Run During R CMD Check
covr modifies your package code for instrumentation. Running it during R CMD check produces unreliable results. Run coverage as a separate step:
# Don't do this in your package's tests!
# Run coverage separately instead
cov <- package_coverage()
Parallel Code
If your package uses parallel processing, set the environment variable:
Sys.setenv(COVR_FIX_PARALLEL_MCEXIT = "true")
# Or
options(covr.fix_parallel_mcexit = TRUE)
Compiled Code Coverage
For C, C++, or FORTRAN code coverage, you need gcov on your system path:
# Set custom gcov path if needed
options(covr.gcov = "/path/to/gcov")
# Disable compiled code coverage if unavailable
options(covr.gcov = "")
Compiled code coverage requires turning off compiler optimization. The covr package handles this automatically when running coverage.
Debugging Installation Issues
If coverage runs fail, use the install_path and clean arguments to debug:
cov <- package_coverage(
path = ".",
install_path = "/tmp/my_package_lib",
clean = FALSE
)
This keeps the temporary installation so you can inspect what went wrong.
Example Workflow
Here’s a typical workflow for improving coverage:
# 1. Calculate current coverage
cov <- package_coverage()
# 2. Find uncovered lines
uncovered <- zero_coverage(cov)
print(uncovered)
# 3. Add tests to cover those lines
# (edit your test files)
# 4. Re-run coverage
cov <- package_coverage()
cov
# 5. Generate report for detailed review
report(cov)
Summary
The covr package provides comprehensive tools for measuring test coverage in R packages. Key functions include package_coverage() for package-level analysis, report() for interactive HTML reports, and zero_coverage() for finding untested code. The package supports flexible exclusion methods, works with any testing framework, and integrates easily with CI services like GitHub Actions and Codecov.
Remember that 100% coverage isn’t always the goal—focus on covering critical paths and edge cases that matter for your package’s reliability.
See Also
- Building R Packages — Learn how to create and structure R packages from scratch
- Debugging in R — Techniques for finding and fixing bugs in your R code
- Shiny Testing — Testing strategies for Shiny applications