Skip to content

Commit b684336

Browse files
authored
Better feedback when testing interactively (#2226)
When a test passes, say so. Includes some refactoring to eliminate a vestigial method, improve testing, and ensure we properly supported nested tests.
1 parent e7e44a3 commit b684336

File tree

9 files changed

+104
-94
lines changed

9 files changed

+104
-94
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# testthat (development version)
22

3+
* When running a test interactively, testthat now reports the number of succeses. The results should also be more useful if you are using nested tests.
34
* The hints generated by `expect_snapshot()` and `expect_snapshot_file()` now include the path to the package, if its not in the current working directory (#1577).
45
* `expect_snapshot_file()` now clearly errors if the `path` doesnt exist (#2191).
56
* `expect_snapshot_file()` now considers `.json` to be a text file (#1593).

R/local.R

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ local_interactive_reporter <- function(.env = parent.frame()) {
201201
reporter <- StopReporter$new()
202202
old <- set_reporter(reporter)
203203
withr::defer(reporter$end_reporter(), envir = .env)
204-
withr::defer(reporter$stop_if_needed(), envir = .env)
205204
withr::defer(set_reporter(old), envir = .env)
206205

207206
reporter

R/praise.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ praise_emoji <- function() {
4343
"\U0001f389", # party popper
4444
"\U0001f38a" # confetti ball
4545
)
46-
sample(emoji, 1)
46+
paste0(" ", sample(emoji, 1))
4747
}
4848

4949
encourage <- function() {

R/reporter-progress.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ spinner <- function(frames, i) {
558558
frames[((i - 1) %% length(frames)) + 1]
559559
}
560560

561-
issue_header <- function(x, pad = FALSE) {
561+
issue_header <- function(x, pad = FALSE, location = TRUE) {
562562
type <- expectation_type(x)
563563
if (has_colour()) {
564564
type <- colourise(first_upper(type), type)
@@ -569,11 +569,11 @@ issue_header <- function(x, pad = FALSE) {
569569
type <- strpad(type, 7)
570570
}
571571

572-
paste0(type, expectation_location(x, " (", ")"), ": ", x$test)
572+
paste0(type, if (location) expectation_location(x, " (", ")"), ": ", x$test)
573573
}
574574

575-
issue_summary <- function(x, rule = FALSE) {
576-
header <- cli::style_bold(issue_header(x))
575+
issue_summary <- function(x, rule = FALSE, location = TRUE) {
576+
header <- cli::style_bold(issue_header(x, location = location))
577577
if (rule) {
578578
# Don't truncate long test names
579579
width <- max(cli::ansi_nchar(header) + 6, getOption("width"))

R/reporter-stop.R

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22
#'
33
#' @description
44
#' The default reporter used when [expect_that()] is run interactively.
5-
#' It responds by [stop()]ping on failures and doing nothing otherwise. This
6-
#' will ensure that a failing test will raise an error.
7-
#'
8-
#' This should be used when doing a quick and dirty test, or during the final
9-
#' automated testing of R CMD check. Otherwise, use a reporter that runs all
10-
#' tests and gives you more context about the problem.
5+
#' It responds by displaying a summary of the number of successes and faiures
6+
#' and [stop()]ping on if there are any failures.
117
#'
128
#' @export
139
#' @family reporters
@@ -21,18 +17,22 @@ StopReporter <- R6::R6Class(
2117
n_fail = 0L,
2218
# Successful expectations
2319
n_success = 0L,
24-
stop_reporter = TRUE,
2520
praise = TRUE,
21+
depth = 0,
2622

27-
initialize = function(stop_reporter = TRUE, praise = TRUE) {
23+
initialize = function(praise = TRUE) {
2824
super$initialize()
2925
self$issues <- Stack$new()
3026
self$praise <- praise
31-
self$stop_reporter <- stop_reporter
3227
},
3328

3429
start_test = function(context, test) {
35-
self$issues <- Stack$new()
30+
if (self$depth == 0) {
31+
self$n_fail <- 0L
32+
self$n_success <- 0L
33+
self$issues <- Stack$new()
34+
}
35+
self$depth <- self$depth + 1
3636
},
3737

3838
add_result = function(context, test, result) {
@@ -45,25 +45,32 @@ StopReporter <- R6::R6Class(
4545
self$n_fail <- self$n_fail + 1
4646
}
4747
self$issues$push(result)
48-
49-
self$local_user_output()
50-
self$cat_line(issue_summary(result, rule = TRUE), "\n")
5148
},
5249

53-
end_reporter = function(context, test) {
50+
end_test = function(context, test) {
51+
self$depth <- self$depth - 1
52+
if (self$depth > 0) {
53+
return()
54+
}
55+
5456
self$local_user_output()
5557

56-
if (self$issues$size() == 0) {
57-
if (self$praise && self$n_success > 0) {
58-
emoji <- praise_emoji()
59-
self$cat_line(colourise("Test passed", "success"), " ", emoji)
60-
}
58+
for (issue in self$issues$as_list()) {
59+
self$cat_line(issue_summary(issue, rule = TRUE, location = FALSE))
60+
}
61+
62+
if (self$praise && self$n_fail == 0 && self$n_success > 0) {
63+
emoji <- praise_emoji()
64+
self$cat_line(cli::format_inline(
65+
"{.strong Test passed with {self$n_success} success{?es}{emoji}}."
66+
))
6167
}
62-
},
6368

64-
stop_if_needed = function() {
65-
if (self$stop_reporter && self$n_fail > 0) {
66-
cli::cli_abort("Test failed.", call = NULL)
69+
if (self$n_fail > 0) {
70+
cli::cli_abort(
71+
"Test failed with {self$n_fail} failure{?s} and {self$n_success} success{?es}.",
72+
call = NULL
73+
)
6774
}
6875
}
6976
)

man/StopReporter.Rd

Lines changed: 2 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 36 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,46 @@
11
# produces useful output
22

3-
-- Failure ('reporters/tests.R:13:3'): Failure:1 -------------------------------
4-
Expected `x` to be TRUE.
5-
Differences:
6-
`actual`: FALSE
7-
`expected`: TRUE
8-
9-
10-
-- Failure ('reporters/tests.R:17:8'): Failure:2a ------------------------------
11-
Expected FALSE to be TRUE.
12-
Differences:
13-
`actual`: FALSE
14-
`expected`: TRUE
15-
16-
Backtrace:
17-
x
18-
1. \-f()
19-
2. \-testthat::expect_true(FALSE)
20-
21-
-- Error ('reporters/tests.R:24:3'): Error:1 -----------------------------------
22-
Error in `eval(code, test_env)`: stop
23-
24-
-- Error ('reporters/tests.R:30:8'): errors get tracebacks ---------------------
25-
Error in `h()`: !
26-
Backtrace:
27-
x
28-
1. \-f()
29-
2. \-g()
30-
3. \-h()
31-
32-
-- Skip ('reporters/tests.R:38:3'): explicit skips are reported ----------------
33-
Reason: skip
34-
35-
-- Skip ('reporters/tests.R:41:1'): empty tests are implicitly skipped ---------
36-
Reason: empty test
37-
38-
-- Warning ('reporters/tests.R:47:5'): warnings get backtraces -----------------
39-
def
40-
Backtrace:
41-
x
42-
1. \-f()
43-
44-
-- Skip ('reporters/tests.R:45:1'): warnings get backtraces --------------------
45-
Reason: empty test
46-
3+
Code
4+
with_reporter("stop", run_tests())
5+
Output
6+
Test passed with 1 success.
7+
-- Failure: Failure:1 ----------------------------------------------------------
8+
Expected `x` to be TRUE.
9+
Differences:
10+
`actual`: FALSE
11+
`expected`: TRUE
12+
13+
Condition
14+
Error:
15+
! Test failed with 1 failure and 0 successes.
4716

48-
# can suppress praise
17+
# works nicely with nested tests
4918

50-
19+
Code
20+
with_reporter("stop", run_tests())
21+
Output
22+
Test passed with 2 successes.
23+
-- Failure: failed then succeeded / failed-1 -----------------------------------
24+
Expected FALSE to be TRUE.
25+
Differences:
26+
`actual`: FALSE
27+
`expected`: TRUE
28+
29+
-- Failure: failed then succeeded / failed-2 -----------------------------------
30+
Expected FALSE to be TRUE.
31+
Differences:
32+
`actual`: FALSE
33+
`expected`: TRUE
34+
35+
Condition
36+
Error:
37+
! Test failed with 2 failures and 1 success.
5138

52-
# stop if needed errors when needed
39+
# errors when needed
5340

5441
Code
55-
r$stop_if_needed()
42+
r$end_test()
5643
Condition
5744
Error:
58-
! Test failed.
45+
! Test failed with 1 failure and 0 successes.
5946

tests/testthat/reporters/nested.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
describe("succeeded", {
2+
it("succeeded-1", expect_true(TRUE))
3+
it("succeeded-2", expect_true(TRUE))
4+
})
5+
6+
describe("failed then succeeded", {
7+
it("failed-1", expect_true(FALSE))
8+
it("failed-2", expect_true(FALSE))
9+
it("succeeded", expect_true(TRUE))
10+
})
Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
1+
# We can't use expect_snapshot_reporter() here because it uses test_one_file()
2+
# which wraps code in `test_code()` which turns the error into a test failure
3+
# It also only captures the output, but we also want to see the error
4+
15
test_that("produces useful output", {
2-
expect_snapshot_reporter(StopReporter$new())
6+
run_tests <- \() source(test_path("reporters/tests.R"))
7+
expect_snapshot(with_reporter("stop", run_tests()), error = TRUE)
38
})
49

510
test_that("can suppress praise", {
6-
expect_snapshot_reporter(
7-
StopReporter$new(praise = FALSE),
8-
test_path("reporters/successes.R")
9-
)
11+
run_tests <- \() source(test_path("reporters/successes.R"))
12+
expect_silent(with_reporter(StopReporter$new(praise = FALSE), run_tests()))
13+
})
14+
15+
test_that("works nicely with nested tests", {
16+
run_tests <- \() source(test_path("reporters/nested.R"))
17+
expect_snapshot(with_reporter("stop", run_tests()), error = TRUE)
1018
})
1119

12-
test_that("stop if needed errors when needed", {
20+
test_that("errors when needed", {
1321
r <- StopReporter$new()
14-
expect_no_error(r$stop_if_needed())
22+
r$start_test()
23+
expect_no_error(r$end_test())
24+
25+
r$start_test()
1526
r$n_fail <- 1
16-
expect_snapshot(error = TRUE, r$stop_if_needed())
17-
r$stop_reporter <- FALSE
18-
expect_no_error(r$stop_if_needed())
27+
r$n_success <- 0
28+
expect_snapshot(error = TRUE, r$end_test())
1929
})

0 commit comments

Comments
 (0)