test_that("scatter_plot creates a ggplot object", {
<- scatter_plot(mtcars, "mpg", "hp", "cyl", 0.7, 3)
p
expect_s3_class(p, "gg")
expect_equal(ggplot2::ggplot_build(p)$layout$panel_ranges[[1]]$x.range,
range(mtcars$mpg))
expect_equal(ggplot2::ggplot_build(p)$layout$panel_ranges[[1]]$y.range,
range(mtcars$hp))
})
test_that("scatter_plot handles different alpha and size values", {
<- scatter_plot(mtcars, "mpg", "hp", "cyl", 0.5, 2)
p1 <- scatter_plot(mtcars, "mpg", "hp", "cyl", 1, 4)
p2
expect_true(p1$layers[[1]]$aes_params$alpha < p2$layers[[1]]$aes_params$alpha)
expect_true(p1$layers[[1]]$aes_params$size < p2$layers[[1]]$aes_params$size)
})
25 Ensure
25.1 Help writing tests
I recommend using the ensure
package to help write tests in your app-package. If you’re already using LLMs to help write code, ensure
is a nice addition to your toolbox because of it’s focus on testing.
Launch app with the shinypak
package:
launch('16.1_test-help')
“The ensurer is familiar with
testthat
3e as well as tidy style, and incorporates context from the rest of your R package to write concise and relevant tests.” -ensure
documentation
Follow the setup instructions to activate the addin (and I highly recommend creating a keyboard shortcut).
25.1.1 Help, not automation
The ensure
package does a great job when writing basic unit tests, but as you’ll see, it doesn’t have a solid understanding of the specification or functional requirements of your application (only you know these). The test code created by ensure
often needs some editing.
For example, the following test code was created for R/scatter_plot.R
function:
Based on the descriptions, these would test two important functional requirements of our application. However, two of these tests fail:
[ FAIL 2 | WARN 0 | SKIP 0 | PASS 3 ]
── Failure (test-scatter_plot.R:6:3): scatter_plot creates a ggplot object ─────
ggplot2::ggplot_build(p)$layout$panel_ranges[[1]]$x.range (`actual`) not equal to range(mtcars$mpg) (`expected`).
`actual` is NULL
`expected` is a double vector (10.4, 33.9)
── Failure (test-scatter_plot.R:8:3): scatter_plot creates a ggplot object ─────
ggplot2::ggplot_build(p)$layout$panel_ranges[[1]]$y.range (`actual`) not equal to range(mtcars$hp) (`expected`).
`actual` is NULL
`expected` is a double vector (52, 335) [ FAIL 2 | WARN 0 | SKIP 0 | PASS 3 ]
This might lead us to believe our utility function is not behaving as expected. But when we dig into the method and values these tests are using, we discover it’s an issue with the test:
# build plot
<- scatter_plot(mtcars, "mpg", "hp", "cyl", 0.7, 3)
p # check x
::ggplot_build(p)$layout$panel_ranges[[1]]$x.range ggplot2
NULL
# check y
::ggplot_build(p)$layout$panel_ranges[[1]]$y.range ggplot2
NULL
The correct code for these tests would be the panel_params
(not panel_ranges
), but these aren’t going to work either, because ggplot2
automatically adjusts the axis limits based on the data and potential aesthetic mappings (which we can confirm with waldo::compare()
):1
::compare(
waldox = ggplot2::ggplot_build(p)$layout$panel_params[[1]]$x.range,
y = range(mtcars$mpg))
`old`: 9.2 35.1 `new`: 10.4 33.9
::compare(
waldox = ggplot2::ggplot_build(p)$layout$panel_params[[1]]$y.range,
y = range(mtcars$hp))
`old`: 37.9 349.1 `new`: 52.0 335.0
We can continue trying to find something produced by ggplot2::ggplot_build()
, but this test confirms the “scatter_plot creates a ggplot object”, so why not just use ggplot2::is.ggplot()
:
test_that("scatter_plot creates a ggplot object", {
<- scatter_plot(mtcars, "mpg", "hp", "cyl", 0.7, 3)
p expect_true(ggplot2::is.ggplot(p))
})test_that("scatter_plot handles different alpha and size values", {
<- scatter_plot(mtcars, "mpg", "hp", "cyl", 0.5, 2)
p1 <- scatter_plot(mtcars, "mpg", "hp", "cyl", 1, 4)
p2 expect_true(p1$layers[[1]]$aes_params$alpha < p2$layers[[1]]$aes_params$alpha)
expect_true(p1$layers[[1]]$aes_params$size < p2$layers[[1]]$aes_params$size)
})
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 3 ]
I’ve provided this example because it illustrates some important limitations when using ensure
(or any LLMs) to help write code: Don’t confuse volume with precision. LLMs are great at generating copius amounts of code, but have no way of checking if the code is functional or accurate. In this case, it arguably would’ve taken less time to do the research and write the correct test (instead of debugging the one created by ensure
).
It’s also worth pointing out that if the original test code did pass, the test_that()
description wouldn’t the expectation (these belong in a “scatter_plot x and y limits match true range of values” test).
Read more about numeric position scales in the Position scales and axes chapter of ggplot2, 3e↩︎