diff --git a/DESCRIPTION b/DESCRIPTION index 4b5482089..4f8a8b754 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: testthat Title: Unit Testing for R -Version: 3.2.1.9000 +Version: 3.2.1.9001 Authors@R: c( person("Hadley", "Wickham", , "hadley@posit.co", role = c("aut", "cre")), person("Posit Software, PBC", role = c("cph", "fnd")), diff --git a/NEWS.md b/NEWS.md index 6304f6b78..0cf93aede 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # testthat (development version) +* `test_dir()` gains a `recursive` argument which allows test files in nested directories (#1605). + ## New expectations * `expect_s7_class()` tests if an object is an S7 class (#1580). diff --git a/R/test-files.R b/R/test-files.R index db231a2d7..5ade31395 100644 --- a/R/test-files.R +++ b/R/test-files.R @@ -26,6 +26,7 @@ #' @param stop_on_failure If `TRUE`, throw an error if any tests fail. #' @param stop_on_warning If `TRUE`, throw an error if any tests generate #' warnings. +#' @param recursive If `TRUE` Test that will search for test files in the nested directories. #' @param load_package Strategy to use for load package code: #' * "none", the default, doesn't load the package. #' * "installed", uses [library()] to load an installed package. @@ -51,6 +52,7 @@ test_dir <- function(path, stop_on_warning = FALSE, wrap = lifecycle::deprecated(), package = NULL, + recursive = FALSE, load_package = c("none", "installed", "source") ) { @@ -62,7 +64,8 @@ test_dir <- function(path, filter = filter, ..., full.names = FALSE, - start_first = start_first + start_first = start_first, + recursive = recursive ) if (length(test_paths) == 0) { abort("No test files found") @@ -379,8 +382,8 @@ local_teardown_env <- function(frame = parent.frame()) { #' @return A character vector of paths #' @keywords internal #' @export -find_test_scripts <- function(path, filter = NULL, invert = FALSE, ..., full.names = TRUE, start_first = NULL) { - files <- dir(path, "^test.*\\.[rR]$", full.names = full.names) +find_test_scripts <- function(path, filter = NULL, invert = FALSE, ..., full.names = TRUE, start_first = NULL, recursive = FALSE) { + files <- dir(path, "^test.*\\.[rR]$", full.names = full.names, recursive = recursive) files <- filter_test_scripts(files, filter, invert, ...) order_test_scripts(files, start_first) } diff --git a/man/find_test_scripts.Rd b/man/find_test_scripts.Rd index 0e5dfc618..007999518 100644 --- a/man/find_test_scripts.Rd +++ b/man/find_test_scripts.Rd @@ -10,7 +10,8 @@ find_test_scripts( invert = FALSE, ..., full.names = TRUE, - start_first = NULL + start_first = NULL, + recursive = FALSE ) } \arguments{ @@ -31,6 +32,8 @@ first pattern first, then the ones matching the second, etc. and then the rest of the files, alphabetically. Parallel tests tend to finish quicker if you start the slowest files first. \code{NULL} means alphabetical order.} + +\item{recursive}{If \code{TRUE} Test that will search for test files in the nested directories.} } \value{ A character vector of paths diff --git a/man/test_dir.Rd b/man/test_dir.Rd index 27fdc5a3f..d599d5b5f 100644 --- a/man/test_dir.Rd +++ b/man/test_dir.Rd @@ -15,6 +15,7 @@ test_dir( stop_on_warning = FALSE, wrap = lifecycle::deprecated(), package = NULL, + recursive = FALSE, load_package = c("none", "installed", "source") ) } @@ -46,6 +47,8 @@ warnings.} \item{package}{If these tests belong to a package, the name of the package.} +\item{recursive}{If \code{TRUE} Test that will search for test files in the nested directories.} + \item{load_package}{Strategy to use for load package code: \itemize{ \item "none", the default, doesn't load the package. diff --git a/tests/testthat/_snaps/test-files.md b/tests/testthat/_snaps/test-files.md index 13e011d73..9958864d6 100644 --- a/tests/testthat/_snaps/test-files.md +++ b/tests/testthat/_snaps/test-files.md @@ -19,3 +19,24 @@ 16 test-helper.R helper test 1 0 FALSE FALSE 0 1 17 test-skip.R Skips skip 1 0 TRUE FALSE 0 0 +# runs all tests in nested directories and records output + + file context test nb failed skipped error warning passed + 1 nested_folder/test-errors.R simple 0 0 FALSE TRUE 0 0 + 2 nested_folder/test-errors.R after one success 1 0 FALSE TRUE 0 1 + 3 nested_folder/test-errors.R after one failure 1 1 FALSE TRUE 0 0 + 4 nested_folder/test-errors.R in the test 0 0 FALSE TRUE 0 0 + 5 nested_folder/test-errors.R in expect_error 1 0 FALSE FALSE 0 1 + 6 nested_folder/test-failures.R just one failure 1 1 FALSE FALSE 0 0 + 7 nested_folder/test-failures.R one failure on two 2 1 FALSE FALSE 0 1 + 8 nested_folder/test-failures.R no failure 2 0 FALSE FALSE 0 2 + 9 nested_folder/test-skip.R Skips skip 1 0 TRUE FALSE 0 0 + 10 test-basic.R logical tests act as expected 2 0 FALSE FALSE 0 2 + 11 test-basic.R logical tests ignore attributes 2 0 FALSE FALSE 0 2 + 12 test-basic.R equality holds 2 0 FALSE FALSE 0 2 + 13 test-basic.R can't access variables from other tests 2 1 0 TRUE FALSE 0 0 + 14 test-basic.R can't access variables from other tests 1 1 0 FALSE FALSE 0 1 + 15 test-empty.R empty test 1 0 TRUE FALSE 0 0 + 16 test-empty.R empty test with error 0 0 FALSE TRUE 0 0 + 17 test-helper.R helper test 1 0 FALSE FALSE 0 1 + diff --git a/tests/testthat/test-test-files.R b/tests/testthat/test-test-files.R index 42f51e076..bddc76501 100644 --- a/tests/testthat/test-test-files.R +++ b/tests/testthat/test-test-files.R @@ -46,6 +46,17 @@ test_that("can control if warnings errors", { expect_error(test_warning(stop_on_warning = FALSE), NA) }) +test_that("runs all tests in nested directories and records output", { + withr::local_envvar(TESTTHAT_PARALLEL = "FALSE") + res <- test_dir(test_path("test_dir_recursive"), reporter = "silent", stop_on_failure = FALSE, recursive = TRUE) + df <- as.data.frame(res) + df$user <- df$system <- df$real <- df$result <- NULL + + local_reproducible_output(width = 200) + local_edition(3) + expect_snapshot_output(print(df)) +}) + # test_file --------------------------------------------------------------- test_that("can test single file", { diff --git a/tests/testthat/test_dir_recursive/helper_hello.R b/tests/testthat/test_dir_recursive/helper_hello.R new file mode 100644 index 000000000..dd96afc4c --- /dev/null +++ b/tests/testthat/test_dir_recursive/helper_hello.R @@ -0,0 +1 @@ +hello <- function() "Hello World" diff --git a/tests/testthat/test_dir_recursive/nested_folder/test-errors.R b/tests/testthat/test_dir_recursive/nested_folder/test-errors.R new file mode 100644 index 000000000..fdeb9d564 --- /dev/null +++ b/tests/testthat/test_dir_recursive/nested_folder/test-errors.R @@ -0,0 +1,22 @@ +test_that("simple", { + stop("argh") +}) + +test_that("after one success", { + expect_true(TRUE) + stop("argh") + expect_true(TRUE) +}) + +test_that("after one failure", { + expect_true(FALSE) + stop("argh") +}) + +test_that("in the test", { + expect_true(stop("Argh")) +}) + +test_that("in expect_error", { + expect_error(stop("Argh")) +}) diff --git a/tests/testthat/test_dir_recursive/nested_folder/test-failures.R b/tests/testthat/test_dir_recursive/nested_folder/test-failures.R new file mode 100644 index 000000000..21204f22f --- /dev/null +++ b/tests/testthat/test_dir_recursive/nested_folder/test-failures.R @@ -0,0 +1,13 @@ +test_that("just one failure", { + expect_true(FALSE) +}) + +test_that("one failure on two", { + expect_false(FALSE) + expect_true(FALSE) +}) + +test_that("no failure", { + expect_false(FALSE) + expect_true(TRUE) +}) diff --git a/tests/testthat/test_dir_recursive/nested_folder/test-skip.R b/tests/testthat/test_dir_recursive/nested_folder/test-skip.R new file mode 100644 index 000000000..5796fa6f1 --- /dev/null +++ b/tests/testthat/test_dir_recursive/nested_folder/test-skip.R @@ -0,0 +1,4 @@ +test_that("Skips skip", { + skip("Skipping to avoid certain failure") + expect_true(FALSE) +}) diff --git a/tests/testthat/test_dir_recursive/test-bare-expectations.R b/tests/testthat/test_dir_recursive/test-bare-expectations.R new file mode 100644 index 000000000..cd99df26e --- /dev/null +++ b/tests/testthat/test_dir_recursive/test-bare-expectations.R @@ -0,0 +1 @@ +expect_equal(2, 2) diff --git a/tests/testthat/test_dir_recursive/test-basic.R b/tests/testthat/test_dir_recursive/test-basic.R new file mode 100644 index 000000000..9baa743e6 --- /dev/null +++ b/tests/testthat/test_dir_recursive/test-basic.R @@ -0,0 +1,22 @@ +test_that("logical tests act as expected", { + expect_true(TRUE) + expect_false(FALSE) +}) + +test_that("logical tests ignore attributes", { + expect_true(c(a = TRUE)) + expect_false(c(a = FALSE)) +}) + +test_that("equality holds", { + expect_equal(5, 5) + expect_identical(10, 10) +}) + +test_that("can't access variables from other tests 2", { + a <- 10 +}) + +test_that("can't access variables from other tests 1", { + expect_false(exists("a")) +}) diff --git a/tests/testthat/test_dir_recursive/test-empty.R b/tests/testthat/test_dir_recursive/test-empty.R new file mode 100644 index 000000000..f31a703e4 --- /dev/null +++ b/tests/testthat/test_dir_recursive/test-empty.R @@ -0,0 +1,3 @@ +test_that("empty test", NULL) + +test_that("empty test with error", stop("Argh")) diff --git a/tests/testthat/test_dir_recursive/test-helper.R b/tests/testthat/test_dir_recursive/test-helper.R new file mode 100644 index 000000000..26da9303f --- /dev/null +++ b/tests/testthat/test_dir_recursive/test-helper.R @@ -0,0 +1,4 @@ +# test that the companion helper script is sourced by test_dir +test_that("helper test", { + expect_equal(hello(), "Hello World") +})