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/ja/skills/test-shiny-app" ~/.claude/skills/pjt222-agent-almanac-test-shiny-app-e9920d && rm -rf "$T"
manifest:
i18n/ja/skills/test-shiny-app/SKILL.mdsource content
Shinyアプリのテスト
shinytest2(エンドツーエンド)とtestServer()(ユニットテスト)を使ってShinyアプリケーションの包括的なテストをセットアップします。
使用タイミング
- 既存のShinyアプリケーションにテストを追加するとき
- 新しいShinyプロジェクトのテスト戦略をセットアップするとき
- Shinyコードをリファクタリングする前にリグレッションテストを書くとき
- ShinyアプリのテストをCI/CDパイプラインに統合するとき
入力
- 必須: Shinyアプリケーションへのパス
- 必須: テストのスコープ(ユニットテスト、エンドツーエンド、または両方)
- オプション: スナップショットテストを使用するか(デフォルト:e2eではあり)
- オプション: CIプラットフォーム(GitHub Actions、GitLab CI)
- オプション: 分離してテストするモジュール
手順
ステップ1: テスト依存関係のインストール
install.packages("shinytest2") # golemアプリの場合、Suggests依存関係として追加 usethis::use_package("shinytest2", type = "Suggests") # testthatインフラがない場合はセットアップ usethis::use_testthat(edition = 3)
期待結果: shinytest2がインストールされ、testthatディレクトリ構造が整っています。
失敗時: shinytest2にはchromore(ヘッドレス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") ), { # 入力を設定 session$setInputs(column = "Species") session$setInputs(value_select = "setosa") session$setInputs(apply = 1) # 出力を確認 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") ), { # モジュールは空のデータでエラーを出してはいけない expect_no_error(session$setInputs(column = "Species")) }) })
主要なパターン:
はブラウザなしでモジュールサーバーロジックをテストするtestServer()
リストでリアクティブな引数を渡すargs
でユーザーインタラクションをシミュレートするsession$setInputs()- リアクティブな戻り値には名前で直接アクセスする
- エッジケースをテストする:空のデータ、NULL入力、無効な値
期待結果:
devtools::test()でモジュールテストが通過します。
失敗時:
testServer()が「not a module server function」でエラーになる場合は、関数が内部でmoduleServer()を使用していることを確認してください。session$setInputs()がリアクティブをトリガーしない場合は、入力を設定した後にsession$flushReact()を追加してください。
ステップ3: shinytest2エンドツーエンドテストの記述
tests/testthat/test-app-e2e.Rを作成します:
test_that("app loads and displays initial state", { # golemアプリの場合 app <- AppDriver$new( app_dir = system.file(package = "myapp"), name = "initial-load", height = 800, width = 1200 ) on.exit(app$stop(), add = TRUE) # アプリが読み込まれるのを待つ app$wait_for_idle(timeout = 10000) # 主要な要素が存在するか確認 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) # アプリと対話する app$set_inputs(`filter1-column` = "cyl") app$wait_for_idle() app$set_inputs(`filter1-apply` = "click") app$wait_for_idle() # 出力値のスナップショット 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_screenshot()ではなくapp$expect_values()を使用してください。
ステップ4: インタラクティブなテスト記録(オプション)
shinytest2::record_test("path/to/app")
これはブラウザで記録パネル付きのアプリを開きます。アプリと対話し、「Save test」をクリックしてテストコードを自動生成します。
期待結果: 記録されたインタラクションを含むテストファイルが
tests/testthat/に生成されます。
失敗時: レコーダーが開かない場合は、まず
shiny::runApp()でアプリが正常に起動することを確認してください。レコーダーには動作するアプリが必要です。
ステップ5: スナップショット管理のセットアップ
スナップショットベースのテストの場合、期待値を管理します:
# レビュー後に新しい/変更されたスナップショットを受け入れる testthat::snapshot_accept("test-app-e2e") # スナップショットの差異をレビュー testthat::snapshot_review("test-app-e2e")
スナップショットディレクトリをバージョン管理に追加します:
tests/testthat/_snaps/ # コミット済み — 期待値を含む
期待結果: スナップショットファイルがリグレッション検出のために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()'
期待結果: ヘッドレスChromeでCIのテストが通過します。
失敗時: よくあるCIの問題:Chromeがインストールされていない(apt-getステップを追加)、タイムアウト(
AppDriver$new()のtimeoutを増やす)。
バリデーション
-
がすべてのテストをエラーなしで実行するdevtools::test() - testServer()テストがモジュールサーバーロジックをカバーする
- shinytest2テストが主要なユーザーワークフローをカバーする
- スナップショットファイルがバージョン管理にコミットされている
- CI環境でテストが通過する
- エッジケースがテストされている(空のデータ、NULL入力、エラー状態)
よくある落とし穴
- ロジックではなくUIレンダリングをテストする: ロジックには
を、データにはtestServer()
を優先してください。視覚的な外観が重要な場合にのみapp$expect_values()
を使用してください — スクリーンショットはプラットフォーム間で壊れやすいです。app$expect_screenshot() - e2eテストでのモジュールID形式: AppDriver経由でモジュールの入力を設定するときは、
ではなく"moduleId.inputId"
形式(ハイフン区切り)を使用してください。"moduleId-inputId" - タイミングの不安定さ:
の後は常にapp$set_inputs()
を呼び出してください。それなしではリアクティブな更新が完了する前にアサーションが実行される可能性があります。app$wait_for_idle() - スナップショットのドリフト: 異なるプラットフォーム(MacとLinux)で生成されたスナップショットをコミットしないでください。スナップショット生成はCIプラットフォームに標準化してください。
- CIでChromeがない: shinytest2はChrome/Chromiumが必要です。CIワークフローには常にインストールステップを含めてください。
関連スキル
— 明確なインターフェースを持つテスト可能なモジュールの作成build-shiny-module
— テストインフラを含むアプリ構造のセットアップscaffold-shiny-app
— Rパッケージの一般的なtestthatパターンwrite-testthat-tests
— Rパッケージ(golemアプリ)のCI/CDセットアップsetup-github-actions-ci