diff --git a/.Rhistory b/.Rhistory deleted file mode 100644 index 5b37168..0000000 --- a/.Rhistory +++ /dev/null @@ -1,69 +0,0 @@ -library(ARTofR) -ARTofR:::ARTofR_user_interface() -ARTofR::xxx_box("For simplicity, I've removed all rows with missing values (i.e. `NaN`s in the `Depth` column & `NA`s in the `BedTemperature` column) before calculating averages. However, exploring and thinking critically about missing data is an important part of data analysis, and in a real-life scenario, you should consider the most appropriate method for handling them.") -ARTofR:::ARTofR_user_interface() -ARTofR::xxx_box1(" # For simplicity, I've removed all rows with missing values (i.e. `NaN`s in # the `Depth` column & `NA`s in the `BedTemperature` column) before # calculating averages. However, exploring and thinking critically about # missing data is an important part of data analysis, and in a real-life # scenario, you should consider the most appropriate method for handling # them. ") -?write.csv -xxx_divider1("SETUP") -#| eval: false -#| echo: true -#..........................load packages......................... -library(palmerpenguins) -library(tidyverse) -#..................practice filtering for island................. -island_df <- penguins %>% -filter(island %in% c("Dream", "Torgesen")) -#........................plot penguin data....................... -ggplot(na.omit(island_df), aes(x = flipper_length_mm, fill = species)) + -geom_histogram(alpha = 0.6, position = "identity", bins = 25) + -scale_fill_manual(values = c("Adelie" = "#FEA346", "Chinstrap" = "#B251F1", "Gentoo" = "#4BA4A4")) + -labs(x = "Flipper length (mm)", y = "Frequency", -fill = "Penguin species") + -myCustomTheme() -#............custom ggplot theme (apply to both plots)........... -myCustomTheme <- function() { -theme_light() + -theme(axis.text = element_text(size = 12), -axis.title = element_text(size = 14, face = "bold"), -legend.title = element_text(size = 14, face = "bold"), -legend.text = element_text(size = 13), -legend.position = "bottom", -panel.border = element_rect(linewidth = 0.7)) -} -#........................plot penguin data....................... -ggplot(na.omit(island_df), aes(x = flipper_length_mm, fill = species)) + -geom_histogram(alpha = 0.6, position = "identity", bins = 25) + -scale_fill_manual(values = c("Adelie" = "#FEA346", "Chinstrap" = "#B251F1", "Gentoo" = "#4BA4A4")) + -labs(x = "Flipper length (mm)", y = "Frequency", -fill = "Penguin species") + -myCustomTheme() -?bc_vars_global -library(fresh) -?bc_vars_global -?bs_vars_global -?bs_vars_navbar -?bs_vars_tabs -library(shiny) -runExample(example = NA) -runExample("01_hello") -#| eval: false -#| echo: true -# load packages ---- -library(palmerpenguins) -library(tidyverse) -# create scatterplot ---- -ggplot(na.omit(penguins), -aes(x = flipper_length_mm, y = bill_length_mm, -color = species, shape = species)) + -geom_point() + -scale_color_manual(values = my_penguin_colors) + -scale_shape_manual(values = my_penguin_shapes) + -labs(x = "Flipper length (mm)", y = "Bill length (mm)", -color = "Penguin species", shape = "Penguin species") + -theme_minimal() + -theme(legend.position = c(0.85, 0.2), -legend.background = element_rect(color = "white")) -?icon -library(shiny) -library(shinytest2) -?testServer diff --git a/course-materials/slides/part1.1-shiny-overview-slides.qmd b/course-materials/slides/part1.1-shiny-overview-slides.qmd index 9d74f42..839745b 100644 --- a/course-materials/slides/part1.1-shiny-overview-slides.qmd +++ b/course-materials/slides/part1.1-shiny-overview-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 1 | January 26^th^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part1.2-setup-app-slides.qmd b/course-materials/slides/part1.2-setup-app-slides.qmd index 2b41962..dbb1814 100644 --- a/course-materials/slides/part1.2-setup-app-slides.qmd +++ b/course-materials/slides/part1.2-setup-app-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 1 | January 26^th^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part2.1-single-file-app-slides.qmd b/course-materials/slides/part2.1-single-file-app-slides.qmd index 897d37e..86ea500 100644 --- a/course-materials/slides/part2.1-single-file-app-slides.qmd +++ b/course-materials/slides/part2.1-single-file-app-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 1 | January 26^th^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part2.2-two-file-app-slides.qmd b/course-materials/slides/part2.2-two-file-app-slides.qmd index 405d210..59bb56c 100644 --- a/course-materials/slides/part2.2-two-file-app-slides.qmd +++ b/course-materials/slides/part2.2-two-file-app-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 1 | January 26^th^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part2.3-deploy-improve-ux-slides.qmd b/course-materials/slides/part2.3-deploy-improve-ux-slides.qmd index ead44a7..0987033 100644 --- a/course-materials/slides/part2.3-deploy-improve-ux-slides.qmd +++ b/course-materials/slides/part2.3-deploy-improve-ux-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 1 | January 26^th^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part3-shiny-dashboard-slides.qmd b/course-materials/slides/part3-shiny-dashboard-slides.qmd index bcf6647..939f0cf 100644 --- a/course-materials/slides/part3-shiny-dashboard-slides.qmd +++ b/course-materials/slides/part3-shiny-dashboard-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part4.1-bslib-fresh-slides.qmd b/course-materials/slides/part4.1-bslib-fresh-slides.qmd index c4c2ad2..d78cc32 100644 --- a/course-materials/slides/part4.1-bslib-fresh-slides.qmd +++ b/course-materials/slides/part4.1-bslib-fresh-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part4.2-css-sass-slides.qmd b/course-materials/slides/part4.2-css-sass-slides.qmd index 91e97bf..a96e986 100644 --- a/course-materials/slides/part4.2-css-sass-slides.qmd +++ b/course-materials/slides/part4.2-css-sass-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part5-ux-ui-slides.qmd b/course-materials/slides/part5-ux-ui-slides.qmd index 91034f2..7e2aa7a 100644 --- a/course-materials/slides/part5-ux-ui-slides.qmd +++ b/course-materials/slides/part5-ux-ui-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part6.1-functions-slides.qmd b/course-materials/slides/part6.1-functions-slides.qmd index 896a3a3..b8e5c5d 100644 --- a/course-materials/slides/part6.1-functions-slides.qmd +++ b/course-materials/slides/part6.1-functions-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part6.2-modules-slides.qmd b/course-materials/slides/part6.2-modules-slides.qmd index 812b9c6..243bbeb 100644 --- a/course-materials/slides/part6.2-modules-slides.qmd +++ b/course-materials/slides/part6.2-modules-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part7.1-debugging-slides.qmd b/course-materials/slides/part7.1-debugging-slides.qmd index bf98702..cbf5266 100644 --- a/course-materials/slides/part7.1-debugging-slides.qmd +++ b/course-materials/slides/part7.1-debugging-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/course-materials/slides/part7.2-unit-testing-slides.qmd b/course-materials/slides/part7.2-unit-testing-slides.qmd index 4dd0d26..a1792be 100644 --- a/course-materials/slides/part7.2-unit-testing-slides.qmd +++ b/course-materials/slides/part7.2-unit-testing-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- @@ -46,15 +46,11 @@ editor_options: . . . -[{{< fa angle-right title="a bullet point" >}}]{.teal-text} have a basic understanding of how to use the `{shinytest2}` package to write unit (regression) tests +[{{< fa angle-right title="a bullet point" >}}]{.teal-text} have a basic understanding of how to use the `{shinytest2}` package to write appropriately scoped unit (regression) tests . . . -[{{< fa angle-right title="a bullet point" >}}]{.teal-text} know how to rerun and update test as necessary - -. . . - -[{{< fa angle-right title="a bullet point" >}}]{.teal-text} TBD +[{{< fa angle-right title="a bullet point" >}}]{.teal-text} know how to rerun and update tests as necessary . . . @@ -91,7 +87,7 @@ There are lots of different *types* of testing. To name just a few: Test can be: - [**Performed manually**, where a person interacts with different app features to identify bugs, errors, anomalies, etc.]{.body-text-s} -- [**Automated**, where a person sets up frameworks and creates test scripts that automate the user actions required for testing a website or app]{.body-text-s} +- [**Automated**, where a person uses test scripts and programs to automate user interactions and validate assumed behaviorwh]{.body-text-s} --- @@ -196,7 +192,9 @@ It's almost inevitable that our app(s) will (at some point) break -- there are l
-The [`{shinytest2}` package](https://rstudio.github.io/shinytest2/articles/shinytest2.html) provides useful tools for **unit testing** Shiny apps. This process is also known as [**regression testing**](https://en.wikipedia.org/wiki/Regression_testing), since we'll be testing existing app behavior for consistency over time (we don't want app behavior to regress). +The [`{shinytest2}` package](https://rstudio.github.io/shinytest2/articles/shinytest2.html) provides useful tools for **unit testing** Shiny apps. We'll use these unit tests to ensure that desired app behavior remains consistent, even after changes to the app's code. + + :::: {.columns} @@ -222,7 +220,11 @@ knitr::include_graphics("images/part6/shinytest2-hex.png") :::: -[With `{shinytest2}`, we can interact with our app via the "app recorder" and our test code will be automatically generated for us. We can then rerun tests to check for consistency as we iterate on our app.]{.body-text-s} +. . . + +::: {.center-text .body-text-s} +*With `{shinytest2}`, we can interact with our app via the "app recorder" and our test code will be automatically generated for us. We can then rerun tests to check for consistency as we iterate on our app.* +::: ::: {.footer} `{shinytest2}` uses [`{chromote}`](https://rstudio.github.io/chromote/) to render your app in a headless [Chromium browser](https://www.chromium.org/chromium-projects/) -- by default, it uses Google Chrome, so make sure you have that installed on your OS! @@ -348,7 +350,7 @@ Let's say that you are satisfied with your work (yay!) and are now ready to writ
-[Before we dive into writing any tests, it's super helpful to inspect your app and, *importantly* jot down (in your own words) any **assertions**. [Test assertions](https://en.wikipedia.org/wiki/Test_assertion) are expressions that contain testable logic to evaluate whether an actual result / behavior is as expected.]{.body-text-s} +Before we dive into writing any tests, it's super helpful to inspect your app and *importantly*, **jot down (in your own words) the expected behaviors of your application**. These will form the basis of the assertions made in your test.
@@ -358,6 +360,8 @@ I find it helpful to frame assertions as: (1) what **actions** can be taken? and
+. . . + ```{r} # countdown::countdown( # minutes = 2, @@ -424,7 +428,7 @@ Over time, **your ability to identify both how users will interact with your app ## {#testing-procedure data-menu-title="Testing procedure"} -[Turn written assertions into actual tests using the `{shinytest2}` workflow]{.slide-title3} +[Turn "plain language" assertions into actual tests using the `{shinytest2}` workflow]{.slide-title3}
@@ -633,7 +637,7 @@ test_that("{shinytest2} recording: one-name-greeting", { # click the actionButton (with Id `greeting_button_input`) app$click("greeting_button_input") - # save the expected input, output, and export values to a JSON snapshot and generates a debug screenshot of the app + # save the expected input, output, and export values to a JSON snapshot and generates a screenshot of the app app$expect_values() }) ``` @@ -1673,23 +1677,23 @@ knitr::include_graphics("images/part6/new.png")
::: {.center-text} -**Our test failed, despite the consistent (expected) behavior of our test subject** (in other words, our test failed, even though the expected output, "Hello Sam!" stayed the same.) +**Our test failed, despite the behavior of our test subject *not* changing** (in other words, our test failed, even though the expected output, "Hello Sam!" stayed the same.) ::: . . . - - - - - +
- +::: {.center-text} +A good test is able to isolate the behavior of it's subject so that you can trust the result of the test. In this case, our tests failed when they shouldn't have... +:::
+. . . + ::: {.center-text} -Because our test failed due to changes outside of the test subject's (i.e. greeting's) behavior, **we need to modify our tests so that they capture a *targeted value expectation*, rather than the state of our entire application**. For example: +Therefore, **we need to modify our tests so that they capture a *targeted value expectation*, rather than the state of our entire application**. For example: ::: :::: {.columns} @@ -2732,6 +2736,15 @@ Should our app's default state change (e.g. if we add a new feature, we update e [{{< fa angle-right title="a bullet point" >}}]{.teal-text} **Use `record_test()` fairly often** -- make a test recording for each feature of your app (many little recordings are encouraged!) +. . . + +
+ +[{{< fa angle-right title="a bullet point" >}}]{.teal-text} **Think of both the most common *and* uncommon pathways that your user may take when interacting with your app.** +E.g. your user types in a name, then deletes everything, then clicks the "Greet" button...would you app handle that use case? + +. . . +
[{{< fa angle-right title="a bullet point" >}}]{.teal-text} **Limit testing to objects under your control.** For example, let's say you have a reactive data frame that you then send to a `DT::datatable` -- if package maintainers update `{DT}`, your output might change which could lead to false positive failed tests. Instead, test just your data frame that gets sent to `{DT}`. diff --git a/course-materials/slides/part8-wrap-up-slides.qmd b/course-materials/slides/part8-wrap-up-slides.qmd index a56f632..465718e 100644 --- a/course-materials/slides/part8-wrap-up-slides.qmd +++ b/course-materials/slides/part8-wrap-up-slides.qmd @@ -19,7 +19,7 @@ editor_options:
-[Week 2 | February 2^nd^, 2024]{.custom-subtitle3} + --- diff --git a/docs/course-materials/slides/part1.1-shiny-overview-slides.html b/docs/course-materials/slides/part1.1-shiny-overview-slides.html index 1ff11f5..4eaedd1 100644 --- a/docs/course-materials/slides/part1.1-shiny-overview-slides.html +++ b/docs/course-materials/slides/part1.1-shiny-overview-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 1.1

What is Shiny?


-

Week 1 | January 26th, 2024

+

@@ -602,7 +602,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part1.2-setup-app-slides.html b/docs/course-materials/slides/part1.2-setup-app-slides.html index 3cfc8fc..0101963 100644 --- a/docs/course-materials/slides/part1.2-setup-app-slides.html +++ b/docs/course-materials/slides/part1.2-setup-app-slides.html @@ -398,7 +398,7 @@

EDS 430: Part 1.2

Setting up a Shiny app


-

Week 1 | January 26th, 2024

+

@@ -586,7 +586,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.1-single-file-app-slides.html b/docs/course-materials/slides/part2.1-single-file-app-slides.html index 2f5415e..3ddd2fc 100644 --- a/docs/course-materials/slides/part2.1-single-file-app-slides.html +++ b/docs/course-materials/slides/part2.1-single-file-app-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 2.1

Building a single-file app


-

Week 1 | January 26th, 2024

+

@@ -1494,7 +1494,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.2-two-file-app-slides.html b/docs/course-materials/slides/part2.2-two-file-app-slides.html index 88db8cb..f5118b7 100644 --- a/docs/course-materials/slides/part2.2-two-file-app-slides.html +++ b/docs/course-materials/slides/part2.2-two-file-app-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 2.2

Building a two-file app


-

Week 1 | January 26th, 2024

+

@@ -1538,7 +1538,7 @@

-
+
05:00
@@ -1848,7 +1848,7 @@

-
+
05:00
@@ -2456,7 +2456,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.2A.html b/docs/course-materials/slides/part2.2A.html index 55c984c..5dc4d6a 100644 --- a/docs/course-materials/slides/part2.2A.html +++ b/docs/course-materials/slides/part2.2A.html @@ -1531,7 +1531,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.2B.html b/docs/course-materials/slides/part2.2B.html index 56bdfd5..8eea1e3 100644 --- a/docs/course-materials/slides/part2.2B.html +++ b/docs/course-materials/slides/part2.2B.html @@ -698,7 +698,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.2C.html b/docs/course-materials/slides/part2.2C.html index 97c63d4..b3a8936 100644 --- a/docs/course-materials/slides/part2.2C.html +++ b/docs/course-materials/slides/part2.2C.html @@ -996,7 +996,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.3-deploy-improve-ux-slides.html b/docs/course-materials/slides/part2.3-deploy-improve-ux-slides.html index 879deb5..5bb765b 100644 --- a/docs/course-materials/slides/part2.3-deploy-improve-ux-slides.html +++ b/docs/course-materials/slides/part2.3-deploy-improve-ux-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 2.3

Deploying apps + improving UX


-

Week 1 | January 26th, 2024

+

@@ -895,7 +895,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part2.3C.html b/docs/course-materials/slides/part2.3C.html index 6d846ae..866dc2e 100644 --- a/docs/course-materials/slides/part2.3C.html +++ b/docs/course-materials/slides/part2.3C.html @@ -375,7 +375,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part3-shiny-dashboard-slides.html b/docs/course-materials/slides/part3-shiny-dashboard-slides.html index 8ebc230..e962818 100644 --- a/docs/course-materials/slides/part3-shiny-dashboard-slides.html +++ b/docs/course-materials/slides/part3-shiny-dashboard-slides.html @@ -414,7 +414,7 @@

EDS 430: Part 3

Building Shiny dashboards


-

Week 2 | February 2nd, 2024

+

@@ -822,8 +822,8 @@

-
- +
+
@@ -881,8 +881,8 @@

-
- +
+
@@ -2444,7 +2444,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part4.1-bslib-fresh-slides.html b/docs/course-materials/slides/part4.1-bslib-fresh-slides.html index b8ad6db..4337349 100644 --- a/docs/course-materials/slides/part4.1-bslib-fresh-slides.html +++ b/docs/course-materials/slides/part4.1-bslib-fresh-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 4.1

Theming and styling apps with {bslib} & {fresh}


-

Week 2 | February 2nd, 2024

+

@@ -878,7 +878,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part4.2-css-sass-slides.html b/docs/course-materials/slides/part4.2-css-sass-slides.html index a2afa5a..e2c76c2 100644 --- a/docs/course-materials/slides/part4.2-css-sass-slides.html +++ b/docs/course-materials/slides/part4.2-css-sass-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 4.2

Styling apps with CSS & Sass


-

Week 2 | February 2nd, 2024

+

@@ -1329,7 +1329,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part5-ux-ui-slides.html b/docs/course-materials/slides/part5-ux-ui-slides.html index ec1e667..570fa53 100644 --- a/docs/course-materials/slides/part5-ux-ui-slides.html +++ b/docs/course-materials/slides/part5-ux-ui-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 5

User-centered design


-

Week 2 | February 2nd, 2024

+

@@ -739,7 +739,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part6.1-functions-slides.html b/docs/course-materials/slides/part6.1-functions-slides.html index 21631f9..c81d7dd 100644 --- a/docs/course-materials/slides/part6.1-functions-slides.html +++ b/docs/course-materials/slides/part6.1-functions-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 6.1

Functions


-

Week 2 | February 2nd, 2024

+

@@ -1247,7 +1247,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part6.2-modules-slides.html b/docs/course-materials/slides/part6.2-modules-slides.html index db62254..a4714e8 100644 --- a/docs/course-materials/slides/part6.2-modules-slides.html +++ b/docs/course-materials/slides/part6.2-modules-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 6.2

Modules


-

Week 2 | February 2nd, 2024

+

@@ -1237,7 +1237,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part7.1-debugging-slides.html b/docs/course-materials/slides/part7.1-debugging-slides.html index d79106c..ade169f 100644 --- a/docs/course-materials/slides/part7.1-debugging-slides.html +++ b/docs/course-materials/slides/part7.1-debugging-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 7.1

Debugging


-

Week 2 | February 2nd, 2024

+

@@ -1272,7 +1272,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part7.2-unit-testing-slides.html b/docs/course-materials/slides/part7.2-unit-testing-slides.html index 5ce26fd..01f6996 100644 --- a/docs/course-materials/slides/part7.2-unit-testing-slides.html +++ b/docs/course-materials/slides/part7.2-unit-testing-slides.html @@ -400,7 +400,7 @@

EDS 430: Part 7.2

Unit Testing


-

Week 2 | February 2nd, 2024

+

@@ -424,13 +424,10 @@

understand some of the reasons why apps break and the benefit of having automated tests

-

have a basic understanding of how to use the {shinytest2} package to write unit (regression) tests

+

have a basic understanding of how to use the {shinytest2} package to write appropriately scoped unit (regression) tests

-

know how to rerun and update test as necessary

-
-
-

TBD

+

know how to rerun and update tests as necessary

@@ -460,7 +457,7 @@

Test can be:

@@ -542,7 +539,8 @@

Enter the {shinytest2} package


-

The {shinytest2} package provides useful tools for unit testing Shiny apps. This process is also known as regression testing, since we’ll be testing existing app behavior for consistency over time (we don’t want app behavior to regress).

+

The {shinytest2} package provides useful tools for unit testing Shiny apps. We’ll use these unit tests to ensure that desired app behavior remains consistent, even after changes to the app’s code.

+


@@ -564,10 +562,14 @@

-

With {shinytest2}, we can interact with our app via the “app recorder” and our test code will be automatically generated for us. We can then rerun tests to check for consistency as we iterate on our app.

+
+
+

With {shinytest2}, we can interact with our app via the “app recorder” and our test code will be automatically generated for us. We can then rerun tests to check for consistency as we iterate on our app.

+
+

@@ -673,11 +675,13 @@

Write down your assertions


-

Before we dive into writing any tests, it’s super helpful to inspect your app and, importantly jot down (in your own words) any assertions. Test assertions are expressions that contain testable logic to evaluate whether an actual result / behavior is as expected.

+

Before we dive into writing any tests, it’s super helpful to inspect your app and importantly, jot down (in your own words) the expected behaviors of your application. These will form the basis of the assertions made in your test.


I find it helpful to frame assertions as: (1) what actions can be taken? and (2) what are the expected outputs / behaviors that result from those actions?


+
+

For example:

@@ -736,7 +740,7 @@

-

Turn written assertions into actual tests using the {shinytest2} workflow

+

Turn “plain language” assertions into actual tests using the {shinytest2} workflow



Generally speaking, that workflow looks something like this:

@@ -887,7 +891,7 @@

# click the actionButton (with Id `greeting_button_input`) app$click("greeting_button_input") - # save the expected input, output, and export values to a JSON snapshot and generates a debug screenshot of the app + # save the expected input, output, and export values to a JSON snapshot and generates a screenshot of the app app$expect_values() }) @@ -1010,7 +1014,7 @@

-
+
03:00
@@ -1401,7 +1405,7 @@

Chat with the person(s) next to you and try to come up with a few different actions / scenarios a user may encounter.

-
+
05:00
@@ -1804,17 +1808,18 @@



-

Our test failed, despite the consistent (expected) behavior of our test subject (in other words, our test failed, even though the expected output, “Hello Sam!” stayed the same.)

+

Our test failed, despite the behavior of our test subject not changing (in other words, our test failed, even though the expected output, “Hello Sam!” stayed the same.)

- - - - -


-

Because our test failed due to changes outside of the test subject’s (i.e. greeting’s) behavior, we need to modify our tests so that they capture a targeted value expectation, rather than the state of our entire application. For example:

+

A good test is able to isolate the behavior of it’s subject so that you can trust the result of the test. In this case, our tests failed when they shouldn’t have…

+
+


+
+
+
+

Therefore, we need to modify our tests so that they capture a targeted value expectation, rather than the state of our entire application. For example:

@@ -2309,7 +2314,7 @@

-
+
03:00
@@ -2741,8 +2746,14 @@



Use record_test() fairly often – make a test recording for each feature of your app (many little recordings are encouraged!)

+
+


+

Think of both the most common and uncommon pathways that your user may take when interacting with your app. E.g. your user types in a name, then deletes everything, then clicks the “Greet” button…would you app handle that use case?

+
+


Limit testing to objects under your control. For example, let’s say you have a reactive data frame that you then send to a DT::datatable – if package maintainers update {DT}, your output might change which could lead to false positive failed tests. Instead, test just your data frame that gets sent to {DT}.

+


@@ -2764,7 +2775,7 @@

-
+
05:00
diff --git a/docs/course-materials/slides/part8-wrap-up-slides.html b/docs/course-materials/slides/part8-wrap-up-slides.html index 9b655de..aea9567 100644 --- a/docs/course-materials/slides/part8-wrap-up-slides.html +++ b/docs/course-materials/slides/part8-wrap-up-slides.html @@ -333,7 +333,7 @@

EDS 430: Part 8

Wrap up


-

Week 2 | February 2nd, 2024

+

diff --git a/docs/search.json b/docs/search.json index af2b56b..29206bf 100644 --- a/docs/search.json +++ b/docs/search.json @@ -81,7 +81,7 @@ "href": "course-materials/slides/part2.1-single-file-app-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 2.1\nBuilding a single-file app\n\nWeek 1 | January 26th, 2024" + "text": "EDS 430: Part 2.1\nBuilding a single-file app" }, { "objectID": "course-materials/slides/part2.1-single-file-app-slides.html#build-app1", @@ -305,7 +305,7 @@ "href": "course-materials/slides/part2.2-two-file-app-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 2.2\nBuilding a two-file app\n\nWeek 1 | January 26th, 2024" + "text": "EDS 430: Part 2.2\nBuilding a two-file app" }, { "objectID": "course-materials/slides/part2.2-two-file-app-slides.html#build-app2", @@ -641,7 +641,7 @@ "href": "course-materials/slides/part6.2-modules-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 6.2\nModules\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 6.2\nModules" }, { "objectID": "course-materials/slides/part6.2-modules-slides.html#modules", @@ -767,7 +767,7 @@ "href": "course-materials/slides/part2.3-deploy-improve-ux-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 2.3\nDeploying apps + improving UX\n\nWeek 1 | January 26th, 2024" + "text": "EDS 430: Part 2.3\nDeploying apps + improving UX" }, { "objectID": "course-materials/slides/part2.3-deploy-improve-ux-slides.html#deploying-apps", @@ -935,7 +935,7 @@ "href": "course-materials/slides/part6.1-functions-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 6.1\nFunctions\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 6.1\nFunctions" }, { "objectID": "course-materials/slides/part6.1-functions-slides.html#functions", @@ -1145,7 +1145,7 @@ "href": "course-materials/slides/part5-ux-ui-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 5\nUser-centered design\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 5\nUser-centered design" }, { "objectID": "course-materials/slides/part5-ux-ui-slides.html#UX-UI", @@ -1243,7 +1243,7 @@ "href": "course-materials/slides/part4.2-css-sass-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 4.2\nStyling apps with CSS & Sass\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 4.2\nStyling apps with CSS & Sass" }, { "objectID": "course-materials/slides/part4.2-css-sass-slides.html#themeing-css-sass", @@ -1369,7 +1369,7 @@ "href": "course-materials/slides/part4.1-bslib-fresh-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 4.1\nTheming and styling apps with {bslib} & {fresh}\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 4.1\nTheming and styling apps with {bslib} & {fresh}" }, { "objectID": "course-materials/slides/part4.1-bslib-fresh-slides.html#themeing-pkgs", @@ -1600,7 +1600,7 @@ "href": "course-materials/slides/part3-shiny-dashboard-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 3\nBuilding Shiny dashboards\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 3\nBuilding Shiny dashboards" }, { "objectID": "course-materials/slides/part3-shiny-dashboard-slides.html#building-dashboards", @@ -1845,7 +1845,7 @@ "href": "course-materials/slides/part8-wrap-up-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 8\nWrap up\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 8\nWrap up" }, { "objectID": "course-materials/slides/part8-wrap-up-slides.html#do-you-need-shiny", @@ -2069,7 +2069,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 7.2\nUnit Testing\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 7.2\nUnit Testing" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#testing", @@ -2083,14 +2083,14 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#LO-testing", "title": "EDS 430", "section": "", - "text": "Learning Objectives for Testing\n\n\nAfter this section, you should:\n\n\n understand some of the reasons why apps break and the benefit of having automated tests\n\n\n have a basic understanding of how to use the {shinytest2} package to write unit (regression) tests\n\n\n know how to rerun and update test as necessary\n\n\n TBD\n\n\n\nPackages introduced:\n\n\n\n {shinytest2}: provides tools for creating and running automated tests on Shiny applications" + "text": "Learning Objectives for Testing\n\n\nAfter this section, you should:\n\n\n understand some of the reasons why apps break and the benefit of having automated tests\n\n\n have a basic understanding of how to use the {shinytest2} package to write appropriately scoped unit (regression) tests\n\n\n know how to rerun and update tests as necessary\n\n\n\nPackages introduced:\n\n\n\n {shinytest2}: provides tools for creating and running automated tests on Shiny applications" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#what-is-testing", "href": "course-materials/slides/part7.2-unit-testing-slides.html#what-is-testing", "title": "EDS 430", "section": "", - "text": "What exactly does “testing” mean?\n\nGenerally speaking, testing refers to the act of evaluating / verifying if a software product or application does what we expect it to do.\n\n\nThere are lots of different types of testing. To name just a few:\n\nUnit testing, where isolated source code is tested to validate expected behavior\nIntegration testing, where various software units or modules are tested as a combined unit (e.g. testing if your app and an API can communicate as intended)\nPerformance testing (which includes things like load testing, stress testing, etc.), where software is tested for its responsiveness and stability under a particular workload\n\n\n\n\nTest can be:\n\nPerformed manually, where a person interacts with different app features to identify bugs, errors, anomalies, etc.\nAutomated, where a person sets up frameworks and creates test scripts that automate the user actions required for testing a website or app" + "text": "What exactly does “testing” mean?\n\nGenerally speaking, testing refers to the act of evaluating / verifying if a software product or application does what we expect it to do.\n\n\nThere are lots of different types of testing. To name just a few:\n\nUnit testing, where isolated source code is tested to validate expected behavior\nIntegration testing, where various software units or modules are tested as a combined unit (e.g. testing if your app and an API can communicate as intended)\nPerformance testing (which includes things like load testing, stress testing, etc.), where software is tested for its responsiveness and stability under a particular workload\n\n\n\n\nTest can be:\n\nPerformed manually, where a person interacts with different app features to identify bugs, errors, anomalies, etc.\nAutomated, where a person uses test scripts and programs to automate user interactions and validate assumed behaviorwh" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#automated-unit-testing", @@ -2118,7 +2118,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#shinytest2", "title": "EDS 430", "section": "", - "text": "Enter the {shinytest2} package\n\nThe {shinytest2} package provides useful tools for unit testing Shiny apps. This process is also known as regression testing, since we’ll be testing existing app behavior for consistency over time (we don’t want app behavior to regress).\n\n\n\n\nFrom the {shinytest2} documentation:\n\n\n“{shinytest2} uses {testthat}’s snapshot-based testing strategy. The first time it runs a set of tests for an application, it performs some scripted interactions with the app and takes one or more snapshots of the application’s state. These snapshots are saved to disk so that future runs of the tests can compare their results to them.”\n\n\n\n\n\n\n\n\n\n\n\n\n\nWith {shinytest2}, we can interact with our app via the “app recorder” and our test code will be automatically generated for us. We can then rerun tests to check for consistency as we iterate on our app.\n\n{shinytest2} uses {chromote} to render your app in a headless Chromium browser – by default, it uses Google Chrome, so make sure you have that installed on your OS!" + "text": "Enter the {shinytest2} package\n\nThe {shinytest2} package provides useful tools for unit testing Shiny apps. We’ll use these unit tests to ensure that desired app behavior remains consistent, even after changes to the app’s code.\n\n\n\n\n\nFrom the {shinytest2} documentation:\n\n\n“{shinytest2} uses {testthat}’s snapshot-based testing strategy. The first time it runs a set of tests for an application, it performs some scripted interactions with the app and takes one or more snapshots of the application’s state. These snapshots are saved to disk so that future runs of the tests can compare their results to them.”\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nWith {shinytest2}, we can interact with our app via the “app recorder” and our test code will be automatically generated for us. We can then rerun tests to check for consistency as we iterate on our app.\n\n\n{shinytest2} uses {chromote} to render your app in a headless Chromium browser – by default, it uses Google Chrome, so make sure you have that installed on your OS!" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#setting-stage", @@ -2139,7 +2139,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#ft1-assertions", "title": "EDS 430", "section": "", - "text": "Write down your assertions\n\nBefore we dive into writing any tests, it’s super helpful to inspect your app and, importantly jot down (in your own words) any assertions. Test assertions are expressions that contain testable logic to evaluate whether an actual result / behavior is as expected.\n\n\nI find it helpful to frame assertions as: (1) what actions can be taken? and (2) what are the expected outputs / behaviors that result from those actions?\n\nFor example:\n\n\n\n\n\n\n\nAction(s)\nExpectation(s)\n\n\n\n\nType [some text value] in text box > click Greet button\nGreeting output is “Hello [some text value]!”\n\n\nType [some text value] in text box > click Greet button > type [some other text value] in text box > click Greet button\nGreeting output is “Hello [some text value]!”, then updates to “Hello [some other text value]!”\n\n\nClick Greet button\nGreeting output is “Hello !”" + "text": "Write down your assertions\n\nBefore we dive into writing any tests, it’s super helpful to inspect your app and importantly, jot down (in your own words) the expected behaviors of your application. These will form the basis of the assertions made in your test.\n\n\nI find it helpful to frame assertions as: (1) what actions can be taken? and (2) what are the expected outputs / behaviors that result from those actions?\n\n\n\nFor example:\n\n\n\n\n\n\n\nAction(s)\nExpectation(s)\n\n\n\n\nType [some text value] in text box > click Greet button\nGreeting output is “Hello [some text value]!”\n\n\nType [some text value] in text box > click Greet button > type [some other text value] in text box > click Greet button\nGreeting output is “Hello [some text value]!”, then updates to “Hello [some other text value]!”\n\n\nClick Greet button\nGreeting output is “Hello !”" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#assertions-note", @@ -2153,7 +2153,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#testing-procedure", "title": "EDS 430", "section": "", - "text": "Turn written assertions into actual tests using the {shinytest2} workflow\n\n\nGenerally speaking, that workflow looks something like this:\n\n\n(1) Run shinytest2::record_test(<app-directory>) in your Console to launch the app recorder in a browser window\n\n\n\n(2) Interact with your application and tell the recorder to make an expectation of the app’s state, which will record input, output, and exported values.\n\n\n\n(3) Give your test a unique name and quit the recorder to save and execute your tests\n\n\n\n\nWe’ll repeat this workflow to write tests for each of our three assertions." + "text": "Turn “plain language” assertions into actual tests using the {shinytest2} workflow\n\n\nGenerally speaking, that workflow looks something like this:\n\n\n(1) Run shinytest2::record_test(<app-directory>) in your Console to launch the app recorder in a browser window\n\n\n\n(2) Interact with your application and tell the recorder to make an expectation of the app’s state, which will record input, output, and exported values.\n\n\n\n(3) Give your test a unique name and quit the recorder to save and execute your tests\n\n\n\n\nWe’ll repeat this workflow to write tests for each of our three assertions." }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#chromote-timeout", @@ -2188,7 +2188,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#test-shinytest2", "title": "EDS 430", "section": "", - "text": "test-shinytest2.R (test code)\n\nAll tests will be saved to tests/testthat/test-shinytest2.R. Yours should look similar to the code below (sans annotations). You may have a different viewport height / width (depending on the size of your viewport when you recorded your test), and if you mistyped / deleted any characters in the textInput, you’ll see multiple app$set_inputs() statements, reflecting these actions:\n\n\n\n\n~/testing-app/tests/testthat/test-shinytest2.R\n\nlibrary(shinytest2)\n\n# runs our test\ntest_that(\"{shinytest2} recording: one-name-greeting\", {\n \n # start Shiny app in a new R session along with chromote's headless browser that's used to simulate user actions\n app <- AppDriver$new(name = \"one-name-greeting\", height = 838, width = 1298)\n \n # set the textInput (with Id `name_input`) to the value `Sam`\n app$set_inputs(name_input = \"Sam\")\n \n # click the actionButton (with Id `greeting_button_input`)\n app$click(\"greeting_button_input\")\n \n # save the expected input, output, and export values to a JSON snapshot and generates a debug screenshot of the app\n app$expect_values()\n})" + "text": "test-shinytest2.R (test code)\n\nAll tests will be saved to tests/testthat/test-shinytest2.R. Yours should look similar to the code below (sans annotations). You may have a different viewport height / width (depending on the size of your viewport when you recorded your test), and if you mistyped / deleted any characters in the textInput, you’ll see multiple app$set_inputs() statements, reflecting these actions:\n\n\n\n\n~/testing-app/tests/testthat/test-shinytest2.R\n\nlibrary(shinytest2)\n\n# runs our test\ntest_that(\"{shinytest2} recording: one-name-greeting\", {\n \n # start Shiny app in a new R session along with chromote's headless browser that's used to simulate user actions\n app <- AppDriver$new(name = \"one-name-greeting\", height = 838, width = 1298)\n \n # set the textInput (with Id `name_input`) to the value `Sam`\n app$set_inputs(name_input = \"Sam\")\n \n # click the actionButton (with Id `greeting_button_input`)\n app$click(\"greeting_button_input\")\n \n # save the expected input, output, and export values to a JSON snapshot and generates a screenshot of the app\n app$expect_values()\n})" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#snaps", @@ -2335,7 +2335,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#scope-too-large2", "title": "EDS 430", "section": "", - "text": "The scope of our assertions was too large\n\n\n\nOur test failed, despite the consistent (expected) behavior of our test subject (in other words, our test failed, even though the expected output, “Hello Sam!” stayed the same.)\n\n\n\n\n\n\n\n\n\nBecause our test failed due to changes outside of the test subject’s (i.e. greeting’s) behavior, we need to modify our tests so that they capture a targeted value expectation, rather than the state of our entire application. For example:\n\n\n\n\ntest_that(\"{shinytest2} recording: one-name-greeting\", {\n app <- AppDriver$new(name = \"one-name-greeting\", height = 815, width = 1276)\n app$set_inputs(name_input = \"Sam\")\n app$click(\"greeting_button_input\")\n app$expect_values()\n})\n\n\n\ntest_that(\"{shinytest2} recording: one-name-greeting\", {\n app <- AppDriver$new(name = \"one-name-greeting\", height = 815, width = 1276)\n app$set_inputs(name_input = \"Sam\")\n app$click(\"greeting_button_input\")\n app$expect_values(output = \"greeting_output\")\n})" + "text": "The scope of our assertions was too large\n\n\n\nOur test failed, despite the behavior of our test subject not changing (in other words, our test failed, even though the expected output, “Hello Sam!” stayed the same.)\n\n\n\n\nA good test is able to isolate the behavior of it’s subject so that you can trust the result of the test. In this case, our tests failed when they shouldn’t have…\n\n\n\n\n\nTherefore, we need to modify our tests so that they capture a targeted value expectation, rather than the state of our entire application. For example:\n\n\n\n\ntest_that(\"{shinytest2} recording: one-name-greeting\", {\n app <- AppDriver$new(name = \"one-name-greeting\", height = 815, width = 1276)\n app$set_inputs(name_input = \"Sam\")\n app$click(\"greeting_button_input\")\n app$expect_values()\n})\n\n\n\ntest_that(\"{shinytest2} recording: one-name-greeting\", {\n app <- AppDriver$new(name = \"one-name-greeting\", height = 815, width = 1276)\n app$set_inputs(name_input = \"Sam\")\n app$click(\"greeting_button_input\")\n app$expect_values(output = \"greeting_output\")\n})" }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#targeted-expectations", @@ -2426,7 +2426,7 @@ "href": "course-materials/slides/part7.2-unit-testing-slides.html#testing-tips", "title": "EDS 430", "section": "", - "text": "Tips for testing\n\n\n Use record_test() fairly often – make a test recording for each feature of your app (many little recordings are encouraged!)\n\n Limit testing to objects under your control. For example, let’s say you have a reactive data frame that you then send to a DT::datatable – if package maintainers update {DT}, your output might change which could lead to false positive failed tests. Instead, test just your data frame that gets sent to {DT}.\n\n\n\n\nThis is only a brief intro to {shinytest2}! Dig into the documentation to learn more." + "text": "Tips for testing\n\n\n Use record_test() fairly often – make a test recording for each feature of your app (many little recordings are encouraged!)\n\n\n Think of both the most common and uncommon pathways that your user may take when interacting with your app. E.g. your user types in a name, then deletes everything, then clicks the “Greet” button…would you app handle that use case?\n\n\n\n Limit testing to objects under your control. For example, let’s say you have a reactive data frame that you then send to a DT::datatable – if package maintainers update {DT}, your output might change which could lead to false positive failed tests. Instead, test just your data frame that gets sent to {DT}.\n\n\n\n\n\nThis is only a brief intro to {shinytest2}! Dig into the documentation to learn more." }, { "objectID": "course-materials/slides/part7.2-unit-testing-slides.html#end", @@ -2685,7 +2685,7 @@ "href": "course-materials/slides/part1.1-shiny-overview-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 1.1\nWhat is Shiny?\n\nWeek 1 | January 26th, 2024" + "text": "EDS 430: Part 1.1\nWhat is Shiny?" }, { "objectID": "course-materials/slides/part1.1-shiny-overview-slides.html#what-is-shiny", @@ -2769,7 +2769,7 @@ "href": "course-materials/slides/part7.1-debugging-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 7.1\nDebugging\n\nWeek 2 | February 2nd, 2024" + "text": "EDS 430: Part 7.1\nDebugging" }, { "objectID": "course-materials/slides/part7.1-debugging-slides.html#debugging", @@ -2916,7 +2916,7 @@ "href": "course-materials/slides/part1.2-setup-app-slides.html#title-slide", "title": "EDS 430", "section": "", - "text": "EDS 430: Part 1.2\nSetting up a Shiny app\n\nWeek 1 | January 26th, 2024" + "text": "EDS 430: Part 1.2\nSetting up a Shiny app" }, { "objectID": "course-materials/slides/part1.2-setup-app-slides.html#setup",