| Title: | Generate Tests from Examples Using 'roxygen' and 'testthat' |
|---|---|
| Description: | Creates 'testthat' tests from 'roxygen' examples using simple tags. |
| Authors: | David Hugh-Jones [aut, cre] |
| Maintainer: | David Hugh-Jones <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.4.0 |
| Built: | 2026-05-26 18:00:21 UTC |
| Source: | https://github.com/hughjonesd/doctest |
The doctest package lets you test the code in your "Examples" section in .Rd files. It uses the roxygen2 and testthat packages. For more information, see @doctest and @expect.
Here's some roxygen documentation for a function:
#' Fibonacci function
#'
#' @param n Integer
#' @return The nth Fibonacci number
#'
#' @doctest
#'
#' @expect type("integer")
#' fib(2)
#'
#' n <- 6
#' @expect equal(8)
#' fib(n)
#'
#' @expect warning("not numeric")
#' fib("a")
#'
#' @expect warning("NA")
#' fib(NA)
fib <- function (n) {
if (! is.numeric(n)) warning("n is not numeric")
...
}
Instead of an @examples section, we have a @doctest section.
This will create tests like:
# Generated by doctest: do not edit by hand
# Please edit file in R/<text>
test_that("Doctest: fib", {
# Created from @doctest for `fib`
# Source file: <text>
expect_type(fib(2), "integer")
n <- 6
expect_equal(fib(n), 8)
expect_warning(fib("a"), "not numeric")
expect_warning(fib(NA), "NA")
})
The .Rd file will be created as normal, with an example section like:
\examples{
fib(2)
n <- 6
fib(n)
fib("a")
fib(NA)
}
Install doctest from r-universe:
install.packages("doctest", repos = c("https://hughjonesd.r-universe.dev",
"https://cloud.r-project.org"))
Or from CRAN:
install.packages("doctest")
Or get the development version:
devtools::install("hughjonesd/doctest")
To use doctest in your package, alter its DESCRIPTION file to add the
dt_roclet roclet and "doctest" package to roxygen:
Roxygen: list(roclets = c("collate", "rd", "namespace",
"doctest::dt_roclet"), packages = "doctest")
Then use roxygen2::roxygenize() or devtools::document() to build
your package documentation.
Maintainer: David Hugh-Jones [email protected]
Useful links:
@doctest starts a doctest: a code example that also contains one or more
testthat expectations.
Use @doctest where you would usually use @examples. Then add
@expect and @expectRaw tags beneath it to create expectations.
By default, a test labelled "Example: <object name>" is created. You
can put a different label after @doctest:
#' @doctest Positive numbers #' #' x <- 1 #' @expect equal(x) #' abs(x) #' #' @doctest Negative numbers #' x <- -1 #' @expect equal(-x) #' abs(x)
You can have more than one @doctest tag in a roxygen block. Each doctest
will create a new test, but they will all be merged into a single Rd example.
Each doctest must contain an independent unit of code. For example, this
won't work:
#' @doctest Test x #' @expect equal(2) #' x <- 1 + 1 #' #' @doctest Keep testing x #' @expect equal(4) #' x^2 #' # Test will error, because `x` has not been defined here
A test will only be written if the @doctest section has at least one
@expect or @expectRaw in it. This lets you change @examples to
@doctest in your code, without generating unexpected tests.
@doctestExample path/to/file.R is a drop-in replacement for
@example path/to/file.R. It doesn't add the contents of file.R to
the test.
If you have complex examples you may want to store them separately.
Roxygen2 uses the @example tag for this. @doctestExample does the
same: it adds the contents of its file to the resulting example.
Suppose man/R/example-code.R contains the line:
2 + 2
Then the following roxygen:
#' @doctest #' #' @expect equal(2) #' 1 + 1 #' @doctestExample man/R/example-code.R
will generate an example like:
1 + 1 2 + 2
At present, @doctestExample doesn't add any code to the tests.
@doctestExample was added in doctest 0.3.0.
You can use this in your package DESCRIPTION like this:
Roxygen: list(roclets = c("collate", "rd", "namespace", "doctest::dt_roclet"))
dt_roclet()dt_roclet()
The doctest roclet
## Not run: roxygen2::roxygenize(roclets = "doctest::dt_roclet") ## End(Not run)## Not run: roxygen2::roxygenize(roclets = "doctest::dt_roclet") ## End(Not run)
@expect creates an expectation for your example code.
Use @expect to create a testthat expectation.
#' @doctest
#'
#' @expect equals(4)
#' 2 + 2
#'
#' f <- function () warning("Watch out")
#' @expect warning()
#' f()
The next expression will be inserted as the first
argument to the expect_* call.
Don't include the expect_ prefix.
If you want to include the expression in a different
place or places, use a dot .:
@expect equals(., rev(.))
c("T", "E", "N", "E", "T")
The @expect tag and code must fit on a single line.
Other expectations:
expectRaw-tag,
snap-tag
@expectRaw creates an expectation for your example code, without adding
the next expression as the subject.
@expectRaw creates a testthat expectation.
Unlike @expect, it doesn't insert the subsequent expression as the first
argument.
#' @doctest
#'
#' x <- 2 + 2
#' @expectRaw equals(x, 4)
#'
#' f <- function () warning("Watch out")
#' @expectRaw warning(f())
Don't include the expect_ prefix.
The @expectRaw tag and code must fit on a single line.
Other expectations:
expect-tag,
snap-tag
@omit excludes example code from a test until the next tag.
Use @resume to restart including code without creating an expectation.
Use @omit to avoid redundant or noisy code:
#' @doctest #' #' @expect equal(0) #' sin(0) #' #' @omit #' curve(sin(x), 0, 2 * pi) #' #' @expect equal(1) #' cos(0)
@omit is separate from \donttest and \dontrun tags in Rd files. This
allows you to test code that would cause an error if run by R CMD CHECK. If
you also want R CMD CHECK to skip your code, you should use \donttest{}
separately (see
writing R extensions).
Remember that the main purpose of examples is to document your package for
your users. If your code is getting too different from your example, consider
splitting it off into a proper test file. You can do this by renaming it and
deleting the Generated by doctest comment.
@snap creates a
snapshot test
for your example. It is shorthand for @expect snapshot().
Often, examples show complex output to the user. If this output changes, you want to check that it still "looks right". Snapshot tests help by failing when the output changes, and allowing you to review and approve the new output.
#' @doctest #' #' @snap #' summary(lm(Petal.Width ~ Species, data = iris))
Other expectations:
expect-tag,
expectRaw-tag
This is a utility function to run doctests in a local source package.
It calls testthat::test_local().
test_doctests(path = ".", ...)test_doctests(path = ".", ...)
path |
Path to package |
... |
Passed to |
The result of testthat::test_local().
## Not run: test_doctests() ## End(Not run)## Not run: test_doctests() ## End(Not run)
@testRaw adds an arbitrary line of code to your test, without including it
in the .Rd example.
@testRaw adds an arbitrary line of code to your test:
#' @doctest
#' @testRaw skip_on_cran("Takes too long")
#' @expect equal(6765)
#' fibonacci(20)
Unless your doctest has at least one @expect or @expectRaw tag, no test
will be created. So use those tags, not @testRaw, to add expectations.
Remember that the main purpose of examples is to document your package for
your users. If your code is getting too different from your example, consider
splitting it off into a proper test file. To do this, rename the
file in tests/testthat, and deleting the Generated by doctest comment.