Loading...
Loading...

Mastering Testing in R

Build reliable, production-grade R code through comprehensive testing strategies from basic assertions to advanced validation frameworks.

1. The Why and How of Testing

Core Principle: Automated verification that your code behaves as expected under various conditions.

Key Testing Types

Test Type Purpose When to Use
Unit Tests Verify individual functions During development
Integration Tests Check component interactions After major features complete
Regression Tests Prevent recurring bugs After bug fixes
# Example function to test
calculate_discount <- function(price, discount_rate) {
  if (discount_rate < 0 | discount_rate > 1) {
    stop("Discount rate must be between 0 and 1")
  }
  price * (1 - discount_rate)
}

2. testthat Framework

Industry Standard: R's most widely-used testing package (part of tidyverse)

Essential Assertions

test_that("Discount calculation works", {
  # Equality
  expect_equal(calculate_discount(100, 0.2), 80)
  
  # Error checking
  expect_error(calculate_discount(100, -0.1))
  
  # Type checking
  expect_type(calculate_discount(100, 0.1), "double")
  
  # Snapshot test
  expect_snapshot(calculate_discount(100, 0.3))
})

Test Organization

my_package/
├── R/
│   └── discounts.R
└── tests/
    ├── testthat/
    │   └── test-discounts.R
    └── testthat.R

3. Advanced Testing Techniques

Test Fixtures

# tests/testthat/helper-discounts.R
create_test_products <- function() {
  list(
    normal = list(price = 100, discount = 0.2),
    premium = list(price = 200, discount = 0.4)
  )
}

# tests/testthat/test-discounts.R
test_that("Discounts apply correctly", {
  products <- create_test_products()
  expect_equal(
    calculate_discount(products$normal$price, products$normal$discount),
    80
  )
})

Parameterized Testing

test_cases <- tibble::tribble(
  ~price, ~discount, ~expected,
  100,    0.1,       90,
  200,    0.25,      150,
  50,     0.5,       25
)

purrr::pwalk(test_cases, function(price, discount, expected) {
  test_that(glue::glue("Discount {discount*100}% works"), {
    expect_equal(calculate_discount(price, discount), expected)
  })
})

4. Performance and Benchmarking

Benchmarking with microbenchmark

library(microbenchmark)

microbenchmark(
  base = calculate_discount(100, 0.2),
  vectorized = Vectorize(calculate_discount)(100, 0.2),
  times = 1000
)

Load Testing

test_that("Function handles load", {
  prices <- runif(10000, 10, 1000) # 10,000 random prices
  discounts <- runif(10000, 0, 0.5)
  
  results <- purrr::map2_dbl(prices, discounts, calculate_discount)
  expect_length(results, 10000)
})

5. Continuous Integration

GitHub Actions Setup

# .github/workflows/R-CMD-check.yaml
name: R-CMD-check

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: r-lib/actions/setup-r@v2
      - uses: r-lib/actions/setup-pandoc@v2
      - name: Install dependencies
        run: |
          install.packages(c("testthat", "devtools"))
          devtools::install_deps(dependencies = TRUE)
      - name: Test
        run: devtools::test()
      - name: Check
        run: devtools::check()

6. Specialized Testing Scenarios

Testing Shiny Apps

library(shinytest2)

test_that("Shiny app works", {
  app <- AppDriver$new()
  app$set_inputs(slider = 50)
  app$click("calculate")
  expect_equal(app$get_value(output = "result"), 150)
})

Testing R Markdown

test_that("Report renders correctly", {
  rmarkdown::render("analysis.Rmd", quiet = TRUE)
  expect_true(file.exists("analysis.html"))
})
0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home