Package 'doctest'

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.3.0
Built: 2024-11-20 05:21:09 UTC
Source: https://github.com/hughjonesd/doctest

Help Index


Write testthat tests for your examples, using roxygen tags

Description

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.

Details

Example

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>
  # Source line: 7
  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)
}

Usage

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.

Doctest is [Experimental].

Author(s)

Maintainer: David Hugh-Jones [email protected]

See Also

Useful links:


Start a doctest

Description

⁠@doctest⁠ starts a doctest: a code example that also contains one or more testthat expectations.

Details

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.


Add an example from a file

Description

⁠@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.

Details

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 after doctest 0.2.0.


Create the doctest roclet

Description

You can use this in your package DESCRIPTION like this:

Roxygen: list(roclets = c("collate", "rd", "namespace", "doctest::dt_roclet"))

Usage

dt_roclet()

Value

The doctest roclet

Examples

## Not run: 
roxygen2::roxygenize(roclets = "doctest::dt_roclet")

## End(Not run)

Create an expectation

Description

⁠@expect⁠ creates an expectation for your example code.

Details

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.

See Also

Other expectations: expectRaw-tag, snap-tag


Create an expectation as-is

Description

⁠@expectRaw⁠ creates an expectation for your example code, without adding the next expression as the subject.

Details

⁠@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.

See Also

Other expectations: expect-tag, snap-tag


Exclude example code from a test

Description

⁠@omit⁠ excludes example code from a test until the next tag. Use ⁠@resume⁠ to restart including code without creating an expectation.

Details

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.


Create a snapshot test

Description

⁠@snap⁠ creates a snapshot test for your example. It is shorthand for ⁠@expect snapshot()⁠.

Details

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))

See Also

Other expectations: expect-tag, expectRaw-tag


Test doctests in a package

Description

This is a utility function to run doctests in a local source package. It calls testthat::test_local().

Usage

test_doctests(path = ".", ...)

Arguments

path

Path to package

...

Passed to testthat::test_local().

Value

The result of testthat::test_local().

Examples

## Not run: 
  test_doctests()

## End(Not run)

Add a line of code to the test

Description

⁠@testRaw⁠ adds an arbitrary line of code to your test, without including it in the .Rd example.

Details

⁠@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.