As a shiny developer
I want to focus on writing tests that matter
So that I can spend less time testing code...
Efficient Testing for Shiny Apps
Martin Frigaard (Atorus)
A bit about me…
As a shiny developer
I want to focus on writing tests that matter
So that I can spend less time testing code...
Shiny testing
Unit tests
Integration tests
System tests
Test tools
Development
Standard app development
Behavior-driven development
Efficient Tests
What should I test?
How should I test it?
Code coverage
…but its not impossible if it’s not
testthat
R/
files and test-
filestestthat
Example: Verifying that a function correctly calculates a specific value based on the input.
shiny
(testServer()
) & testtthat
Example: Making sure that modules communicate and display the results from a specific function or calculation
Package(s): shiny
& shinytest2
Confirms all parts of the app behave correctly and provide a good user experience.
Example: Simulating a user’s experience with the application, selecting inputs, entering data, and ensuring the application responds correctly and displays the expected outputs.
Test fixtures are used to create repeatable test conditions
My app has the following utility function for creating a ggplot2
scatter plot:
The data masking from rlang
(.data[[ ]]
) means it can handle string arguments (i.e. input$x
and input$y
)
Test fixtures can be stored in tests/testthat/fixtures/
The make-tidy_ggp2_movies.R
creates a ‘tidy’ version of ggplot2movies::movies
.
Static data fixtures can be accessed with testthat::test_path()
:
test_that("tidy_ggp2_movies.rds works", code = {
tidy_ggp2_movies <- readRDS(test_path("fixtures", "tidy_ggp2_movies.rds"))
app_graph <- scatter_plot(tidy_ggp2_movies,
x_var = 'rating',
y_var = 'budget',
col_var = 'mpaa',
alpha_var = 3/4,
size_var = 2.5)
expect_true(ggplot2::is.ggplot(app_graph))
})
tidy_ggp2_movies.rds
is used in a few tests, move make-tidy_ggp2_movies.R
into data-raw/
and make tidy_ggp2_movies
part of the packageggplot2::is.ggplot()
confirms a plot object has been built (doesn’t require a snapshot test)Test helpers reduce repeated/duplicated test code
Objects that aren’t large enough to justify storing as static test fixtures can be created with helper functions
This removes duplicated code…
test_that("scatter_plot() works", code = {
tidy_ggp2_movies <- readRDS(test_path("fixtures", "tidy_ggp2_movies.rds"))
app_graph <- scatter_plot(tidy_ggp2_movies,
x_var = var_inputs()$x,
y_var = var_inputs()$y,
col_var = var_inputs()$z,
alpha_var = var_inputs()$alpha,
size_var = var_inputs()$size)
testthat::expect_true(ggplot2::is.ggplot(app_graph))
})
…but it’s unclear where var_inputs()
comes from (or what it contains)
If you have repeated code in your tests, consider the following questions below before creating a helper function:
Does the code help explain what behavior is being tested?
Would a helper make it harder to debug the test when it fails?
Consider a function like make_ggp2_inputs()
:
list(x = 'rating',
y = 'length',
z = 'mpaa',
alpha = 0.75,
size = 3,
plot_title = 'Enter plot title'
)
Focuses on coding an applications functionalities
Create applications that meet desired behaviors
Users and developers work together to develop a clear vision of app’s value
Ongoing discussions between users and developers improves understanding of the problem by:
Features are tangible functionalities that facilitate achieving a business goal
Users and developers write stories to describe a feature’s expected outcome
Describe application behaviors in a plain, human-readable format
Set the stage
Efficient testing means writing scenarios that cover critical paths.
testthat
BDD supporttestthat
has describe()
and it()
functions for features and scenarios:
testthat::describe(
"Feature: Scatter plot data visualization
As a film data analyst
I want to explore movie review data from IMDB.com
So that I can analyze relationships between movie reivew metrics"
code = {
testthat::it(
"Scenario: Create scatter plot
Given I have launched the movie review exploration app,
When I view the scatter plot,
Then I should see points representing values for a default
set of continuous and categorical columns.",
code = {
# test code
})
})
shiny::testServer()
tests should focus on ‘handshakes’ between modules and exporting/saving datashinytest2()
Today’s functionality is tomorrow’s regression
Apps need thorough and continuous testing in development so new features don’t negatively affect the existing functionalities