Agent-almanac test-shiny-app
install
source · Clone the upstream repo
git clone https://github.com/pjt222/agent-almanac
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/zh-CN/skills/test-shiny-app" ~/.claude/skills/pjt222-agent-almanac-test-shiny-app-1d1964 && rm -rf "$T"
manifest:
i18n/zh-CN/skills/test-shiny-app/SKILL.mdsource content
测试 Shiny 应用
使用 shinytest2(端到端)和 testServer()(单元测试)为 Shiny 应用程序设置全面测试。
适用场景
- 为现有 Shiny 应用添加测试
- 为新 Shiny 项目设置测试策略
- 在重构 Shiny 代码前编写回归测试
- 将 Shiny 应用测试集成到 CI/CD 流水线
输入
- 必需:Shiny 应用路径
- 必需:测试范围(单元测试、端到端,或两者)
- 可选:是否使用快照测试(默认:端到端测试是)
- 可选:CI 平台(GitHub Actions、GitLab CI)
- 可选:要单独测试的模块
步骤
第 1 步:安装测试依赖项
install.packages("shinytest2") # For golem apps, add as a Suggests dependency usethis::use_package("shinytest2", type = "Suggests") # Set up testthat infrastructure if not present usethis::use_testthat(edition = 3)
预期结果: shinytest2 已安装,testthat 目录结构就绪。
失败处理: shinytest2 需要 chromote(无头 Chrome)。在系统上安装 Chrome/Chromium。WSL 上:
sudo apt install -y chromium-browser。使用 chromote::find_chrome() 验证。
第 2 步:用 testServer() 为模块编写单元测试
创建
tests/testthat/test-mod_dashboard.R:
test_that("dashboard module filters data correctly", { testServer(dataFilterServer, args = list( data = reactive(iris), columns = c("Species", "Sepal.Length") ), { # Set inputs session$setInputs(column = "Species") session$setInputs(value_select = "setosa") session$setInputs(apply = 1) # Check output result <- filtered() expect_equal(nrow(result), 50) expect_true(all(result$Species == "setosa")) }) }) test_that("dashboard module handles empty data", { testServer(dataFilterServer, args = list( data = reactive(iris[0, ]), columns = c("Species") ), { # Module should not error on empty data expect_no_error(session$setInputs(column = "Species")) }) })
关键模式:
无需浏览器测试模块 server 逻辑testServer()- 通过
列表传递响应式参数args - 使用
模拟用户交互session$setInputs() - 直接按名称访问响应式返回值
- 测试边界情况:空数据、NULL 输入、无效值
预期结果: 模块测试通过
devtools::test()。
失败处理: 如果
testServer() 报错"不是模块 server 函数",确保函数内部使用了 moduleServer()。如果 session$setInputs() 没有触发响应式,在设置输入后添加 session$flushReact()。
第 3 步:编写 shinytest2 端到端测试
创建
tests/testthat/test-app-e2e.R:
test_that("app loads and displays initial state", { # For golem apps app <- AppDriver$new( app_dir = system.file(package = "myapp"), name = "initial-load", height = 800, width = 1200 ) on.exit(app$stop(), add = TRUE) # Wait for app to load app$wait_for_idle(timeout = 10000) # Check that key elements exist app$expect_values() }) test_that("filter interaction updates the table", { app <- AppDriver$new( app_dir = system.file(package = "myapp"), name = "filter-interaction" ) on.exit(app$stop(), add = TRUE) # Interact with the app app$set_inputs(`filter1-column` = "cyl") app$wait_for_idle() app$set_inputs(`filter1-apply` = "click") app$wait_for_idle() # Snapshot the output values app$expect_values(output = "table") })
关键模式:
在无头 Chrome 中启动应用AppDriver$new()- 始终使用
进行清理on.exit(app$stop()) - 模块输入 ID 使用格式
"moduleId-inputId"
创建/比较快照文件app$expect_values()
确保响应式更新完成app$wait_for_idle()
预期结果: 端到端测试在
tests/testthat/_snaps/ 中创建快照文件。
失败处理: 如果找不到 Chrome,将
CHROMOTE_CHROME 环境变量设置为 Chrome 二进制文件路径。如果快照在 CI 上失败但本地通过,检查平台相关的渲染差异——使用 app$expect_values() 进行数据快照而非 app$expect_screenshot() 进行视觉快照。
第 4 步:交互式录制测试(可选)
shinytest2::record_test("path/to/app")
这会在带有录制面板的浏览器中打开应用。与应用交互,然后单击"保存测试"以自动生成测试代码。
预期结果: 在
tests/testthat/ 中生成包含录制交互的测试文件。
失败处理: 如果录制器未打开,先用
shiny::runApp() 确认应用能正常运行。录制器需要工作正常的应用。
第 5 步:设置快照管理
对于基于快照的测试,管理期望值:
# Accept new/changed snapshots after review testthat::snapshot_accept("test-app-e2e") # Review snapshot differences testthat::snapshot_review("test-app-e2e")
将快照目录添加到版本控制:
tests/testthat/_snaps/ # Committed — contains expected values
预期结果: 快照文件在 git 中被跟踪,用于回归检测。
失败处理: 如果快照意外更改,运行
testthat::snapshot_review() 查看差异。使用 testthat::snapshot_accept() 接受有意更改。
第 6 步:与 CI 集成
添加到
.github/workflows/R-CMD-check.yaml 或创建专用工作流:
- name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y chromium-browser - name: Set Chrome path run: echo "CHROMOTE_CHROME=$(which chromium-browser)" >> $GITHUB_ENV - name: Run tests run: | Rscript -e 'devtools::test()'
对于 golem 应用,确保在测试前安装应用包:
- name: Install app package run: Rscript -e 'devtools::install()'
预期结果: 测试在 CI 上以无头 Chrome 通过。
失败处理: 常见 CI 问题:Chrome 未安装(添加 apt-get 步骤)、显示服务器缺失(shinytest2 默认使用无头模式,通常不是问题)、或慢速运行器上超时(增加
AppDriver$new() 中的 timeout)。
验证清单
-
运行所有测试无错误devtools::test() - testServer() 测试覆盖模块 server 逻辑
- shinytest2 测试覆盖关键用户工作流
- 快照文件已提交到版本控制
- 测试在 CI 环境中通过
- 测试了边界情况(空数据、NULL 输入、错误状态)
常见问题
- 测试 UI 渲染而非逻辑:优先用
测试逻辑,用testServer()
测试数据。只在视觉外观重要时使用app$expect_values()
——截图在不同平台上易碎。app$expect_screenshot() - 端到端测试中的模块 ID 格式:通过 AppDriver 设置模块输入时,使用
格式(连字符分隔),而非"moduleId-inputId"
。"moduleId.inputId" - 不稳定的时序:在
后始终调用app$set_inputs()
。否则,断言可能在响应式更新完成前运行。app$wait_for_idle() - 快照漂移:不要提交在不同平台(Mac vs Linux)上生成的快照。以 CI 平台为标准生成快照。
- CI 上缺少 Chrome:shinytest2 需要 Chrome/Chromium。在 CI 工作流中始终包含安装步骤。
相关技能
— 创建具有清晰接口的可测试模块build-shiny-module
— 设置带测试基础设施的应用结构scaffold-shiny-app
— R 包的通用 testthat 模式write-testthat-tests
— R 包的 CI/CD 设置(golem 应用)setup-github-actions-ci