R packages,
good vibes only

Maëlle Salmon (rOpenSci)

https://uros-maelle.netlify.app

Construction blocks

What is quality software?

Production line of packages with people assessing them

rOpenSci dev guides

https://devguide.ropensci.org

https://stats-devguide.ropensci.org/

  • Before: Several Markdown files.

  • Today: A whole book, multilingual now! Plus the stat dev guide.

Example criterion:
well-tested package

Jenga wooden blocks

Example criterion:
well-tested package

What does this mean?

Jenny Bryan on testing

If you use software that lacks automated tests, you are the tests.

Jenny Bryan, Twitter.

What is testing?

Informal testing

cool_function <- function(x) {
  if (!is.numeric(x)) {
    cli::cli_abort(
      "{.arg x} must be numeric, 
      not {.obj_type_friendly {x}}.",
      class = "uncool"
    )
  }
  x + 2
}

Informal testing (1/∞)

cool_function(2)
[1] 4
cool_function("not-a-number")
Error in `cool_function()`:
! `x` must be numeric, not a string.

Informal testing (2/∞)

cool_function <- function(x) {
  if (!is.numeric(x)) {
    cli::cli_abort(
      "{.arg x} must be numeric, 
      not {.obj_type_friendly {x}}.",
      class = "uncool"
    )
  }
  2 + x
}

Informal testing (2/∞)

cool_function(2)
[1] 4
cool_function("not-a-number")
Error in `cool_function()`:
! `x` must be numeric, not a string.

Informal testing

Sisyphus pushing a rock on a mountain

Automated testing!

Crane carrying a rock over a mountain

rOpenSci dev guide on testing

All packages should have a test suite that covers major functionality of the package. The tests should also cover the behavior of the package in case of errors.

A test example

Using the testthat package

test_that("cool_function() works", {
  expect_equal(cool_function(2), 4)
})
Test passed with 1 success 🌈.

Failing test

cool_function <- function(x) {
  if (!is.numeric(x)) {
    cli::cli_abort(
      "{.arg x} must be numeric, not {.obj_type_friendly {x}}.",
      class = "uncool"
    )
  }
  x + 3
}

Failing test

test_that("cool_function() works", {
  expect_equal(cool_function(2), 4)
})
── Failure: cool_function() works ──────────────────────────────────────────────
Expected `cool_function(2)` to equal 4.
Differences:
1/1 mismatches
[1] 5 - 4 == 1
Error:
! Test failed with 1 failure and 0 successes.

Running the tests

devtools::test_active_file() # current test file / script
devtools::test() # all the tests
devtools::check() # R CMD check

A test example: error

test_that("cool_function() errors well", {
  expect_error(
    cool_function("not-a-number"),
    class = "uncool"
  )
})
Test passed with 1 success 🥳.

A test example: error message/formatting

📸

test_that("cool_function() errors well", {
  expect_snapshot(error = TRUE, {
    cool_function("not-a-number")
  })
})

Where’s the automation? 😅

  • An atomic habit 💪

  • Using continuous integration ☁️

Continuous integration

Your tests run on the cloud every time you make a change!

usethis::use_github_action("check-standard")

rOpenSci dev guide on testing

It is good practice to write unit tests for all functions, and all package code in general, ensuring key functionality is covered. Test coverage below 75% will likely require additional tests or explanation before being sent for review.

Test coverage?!

No test, no coverage.

Script with many lines

Test coverage

expect_that(cool_function(2), 4)

Script with part of the last lines highlighted in green

Test coverage

expect_type(cool_function(2), "numeric")

Script with part of the last lines highlighted in green

Test coverage

expect_that(cool_function(2), 4)

expect_error(cool_function("a"), class = "uncool")

Script with all lines highlighted in green

Computing the coverage

{covr}

devtools::test_coverage_active_file()
devtools::test_coverage()
usethis::use_github_action("test-coverage") + codecov.io

rOpenSci dev guide on testing

Tests should be easy to understand, and as self-contained as possible. When using testthat, avoid using code outside of test_that() blocks (such as pre-processing steps). We recommend reading the high-level principles for testing in the R Packages book.

(“R Packages” by Hadley Wickham and Jenny Bryan)

Hannah Frick’s LatinR 23 keynote

library(dplyr)
dat <- data.frame(a = 1:3, b = c("a", "b", "c"))
skip_if_not_installed("a_package")
test_that("my_fun() does this", {
  expect_equal(my_fun(dat), ...)
})
dat2 <- data.frame(x = 1:5, y = 6:10)
skip_on_os("windows")
test_that("my_fun_2() does that", {
  dat2 <- mutate(dat2, z = x + y)
  expect_equal(my_fun_2(dat, dat2), ...)
})

Hannah Frick’s LatinR 23 keynote

test_that("my_fun() does this", {
  skip_if_not_installed("a_package")
  dat <- data.frame(a = 1:3, b = c("a", "b", "c"))
  expect_equal(my_fun(dat), ...)
})
test_that("my_fun_2() does that", {
  skip_if_not_installed("a_package")
  skip_on_os("windows")
  dat <- data.frame(a = 1:3, b = c("a", "b", "c"))
  dat2 <- data.frame(x = 1:5, y = 6:10)
  dat2 <- dplyr::mutate(dat2, z = x + y)
  expect_equal(my_fun_2(dat, dat2), ...)
})

Testing: a pillar of good software

Key for

  • A good user experience;
  • A good developer experience (including with LLMs).

rOpenSci dev guide
on testing tools

  • Frameworks: testthat but also tinytest.
  • Helpers: withr, dittodb, vcr…

rOpenSci dev guide

Your one-stop! 😎

  • Standards
  • Resources
  • Tools

Standards are good

Colorful Stack Toy on Blue Background

Standards are good

But only when applied!

rOpenSci software peer-review

https://ropensci.org/software-review

  • Improve quality of packages in scope

  • Build a community of practice

rOpenSci software peer-review

Building blocks of an rOpenSci package review: submission, automatic checks, editor checks, informal back and forth, reviewer 1, reviewer 2, informal back and forth, author response, reviewer 1's approval, reviewer 2's approval, editor's approval, TODO list by bot

Adherence to standards checked by…

  • automation! {pkgcheck}

  • reviewers’ expertise and outside perspective

rOpenSci software peer-review

You can participate!

  • Submit packages;

  • Volunteer as a reviewer.

Your review system?

Your review system?

  • Guidelines like ours.

  • Review process somewhere. ChatOps?

  • End goal: registering the package.

Your guidelines: example

https://intro-programacion.netlify.app/d_checklist by Paola Corrales & Yanina Bellini Saibene

Tooling: example

https://github.com/paocorrales/jtp by Paola Corrales

How to register packages?

Package approval at rOpenSci

Git diff of a JSON file called packages.json, where a new entry corresponding to a new package was added.

A JSON for what?

R-universe package manifest

https://ropensci.r-universe.dev

R-universe

https://ropensci.org/r-universe

  • A way to publish R packages

  • A way to discover R packages

R-universe set-up

  • Develop packages on any public Git platform

  • Install R-universe’s GitHub App

  • Create packages.json

packages.json

[
    {
        "package": "curl",
        "url": "https://github.com/jeroen/curl"
    },
    {
        "package": "pdftools",
        "url": "https://github.com/ropensci/pdftools"
    }
]

Your review system

  • Guidelines like ours.

  • Review process somewhere.

  • End goal: registering the package within your R-universe.

Review system beyond tech: tone

Pastel pink construction blocks

Community over code

““Community over code”. In brief, this means that the most successful long-lived projects value a broad and collaborative community over the details of the code itself.”

The Apache Way https://theapacheway.com/community-over-code/

How to build a
constructive review process?

  • Clarity + transparency. Clear standards, clear processes.

  • Kindness

Code review kindness

rOpenSci’s community is our best asset. We aim for reviews to be open, non-adversarial, and focused on improving software quality. Be respectful and kind!

rOpenSci dev guide

Code review anxiety

The Code Review Anxiety Workbook by Carol Lee and Kristen Foster-Marks.

After review: software lifecycle

Decorative chick in a Basket on a Wooden Table with Eggs

Spring cleaning

Tackle maintenance tasks regularly!

https://tidyverse.org/blog/2023/06/spring-cleaning-2023/

https://ropensci.org/events/coworking-2023-05/

Make succession easier

  • Have a good codebase 😉

  • Foster a welcoming atmosphere for contributors. 2021 community call

  • Help train new contributors. rOpenSci champions program

  • Regularly assess your maintenance responsibilities. rOpenSci yearly package maintainer survey.

Lifecycle of external software

A complex construction made of blocks entitled 'all modern infrastructure', with a small block at the basis labelled 'A project some random person in Nebraska has been thanklessly maintaining since 2003'

“Dependency”, Randall Munroe, xkcd.com

Lifecycle of external software

R-spatial evolution

The legacy R spatial infrastructure packages maptools, rgdal and rgeos were archived by CRAN on Monday, October 16, 2023.

https://github.com/r-spatial/evolution

Keep in touch with the ecosystem

Summary: package collections

White and Black Checkered Brick Toys Stacked on Top of Each Other Forming Ladder

Summary: package collections

  • Standards
  • Processes
  • Clarity and kindness

Take advantage of rOpenSci resources, examples and infrastructure!

Construction blocks

Thank you!! https://uros-maelle.netlify.app

And thanks to: Hannah Frick, Yanina Bellini Saibene.