Skip to Content
Course content

71: Continuous Integration for R Packages

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I can't tell you how many times I've seen a developer push a "small fix" to an R package, only for the entire project to grind to a halt because the code worked perfectly on their local machine but failed miserably for everyone else. We've all been there. You've run devtools::check(), you've run your tests, and everything looks green. You push to GitHub, feeling confident, and then a colleague pings you ten minutes later saying the package won't even install on their Linux box.

The "It Works on My Machine" Gamble

The naive way to handle quality control is the "manual ritual." This is where you, the developer, are the sole arbiter of truth. Before every push, you manually run your test suite and a package check. It feels efficient because it's fast—you're using a cached library and a local environment that you've spent weeks tweaking. But this approach has a massive blind spot: your environment is "polluted." You might have a version of dplyr installed that is slightly newer than what's on CRAN, or perhaps you have a system dependency installed in /usr/local/bin that you completely forgot about.

When you rely on manual checks, you aren't actually testing if the package is portable; you're testing if the package works for you. The moment your code hits a clean environment, those hidden dependencies surface as screaming errors. I've seen this happen most often with file paths—someone hardcodes a path that works on their macOS setup, and the whole thing explodes the moment it tries to run on a Ubuntu server.

Outsourcing the Trust to a Clean Slate

The better way is to stop trusting yourself and start trusting a neutral third party. In the R ecosystem, this usually means GitHub Actions. Instead of you saying "it's ready," you tell GitHub: "Every time I push code, spin up a fresh virtual machine, install R from scratch, install my dependencies, and run R CMD check."

Using usethis::use_github_action_check_standard() is the gold standard here. It doesn't just run your tests; it runs the full gauntlet of checks that CRAN would perform. It checks for documentation gaps, ensures your NAMESPACE is correct, and verifies that your package actually installs. I prefer this because it shifts the burden of proof. If the CI (Continuous Integration) badge is red, the code is broken—period. It doesn't matter if it works on your laptop. The "source of truth" is now the clean runner, not the developer's workstation.

The Cost of Certainty

Now, let's be honest about the trade-off: CI adds friction. You can't just push and forget; you have to wait five or ten minutes for the runners to finish. Sometimes, the CI will fail because a CRAN mirror is temporarily down or a dependency update broke something you didn't even touch. It can be infuriating to see a red 'X' on your commit when you know your code is fine.

But I'll take that frustration over the alternative. I'd much rather spend five minutes waiting for a GitHub Action to fail than spend two hours on a Zoom call with a teammate trying to figure out why the package won't build on their machine. The "cost" of CI is just a bit of time and some occasional noise, but the "profit" is the absolute certainty that your package is actually distributable.




📋 Practical Task

Breaking and Fixing the climate_calc CI Pipeline

In this exercise, you will implement a CI pipeline for a hypothetical package called climate_calc and intentionally trigger a failure to see the feedback loop in action.

  • Setup: Create a basic R package structure named climate_calc. Add a simple function that calculates a temperature average and a corresponding test file in tests/testthat/ that verifies this calculation.
  • Automation: Use usethis::use_github_action_check_standard() to generate the necessary YAML workflow file.
  • The First Pass: Push your code to a GitHub repository. Monitor the "Actions" tab to ensure the build passes.
  • The Sabotage: Intentionally break the package. You can do this by adding a function call to a package that isn't listed in your DESCRIPTION file, or by changing the expected value in your test file so it no longer matches the function output.
  • The Verification: Push the broken code. Wait for the CI to fail, then examine the logs in GitHub Actions to identify exactly which check failed (e.g., R CMD check or testthat).
  • The Resolution: Fix the error, push again, and verify that the pipeline returns to a green state.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.