shinytest2로 테스트
앱이 커질수록 수동으로 클릭해서 확인하는 방식은 한계에 부딪힙니다. 필터를 바꿨을 때 차트가 제대로 갱신되는지, 파일을 업로드했을 때 테이블이 나타나는지를 코드로 검증할 수 있으면 배포 전 자신감이 생깁니다. shinytest2는 Shiny 앱 전용 자동화 테스트 패키지입니다.
설치
install.packages("shinytest2")
shinytest2는 내부적으로 chromote(Chromium 기반 브라우저)를 씁니다. Google Chrome 또는 Chromium이 설치되어 있어야 합니다.
첫 테스트 만들기
테스트 파일은 tests/testthat/ 폴더에 위치합니다.
modular-app/
├── app.R
├── global.R
└── tests/
└── testthat/
└── test-app.R
shinytest2::record_test()를 실행하면 브라우저가 열리고, 클릭하는 과정을 녹화해 테스트 코드를 자동 생성합니다.
# RStudio 콘솔에서 실행
shinytest2::record_test("modular-app")
테스트 파일 구조
# 새 파일: modular-app/tests/testthat/test-app.R
library(shinytest2)
test_that("필터 변경 시 차트 갱신", {
app <- AppDriver$new(
app_dir = ".", # app.R이 있는 폴더
name = "filter-test",
height = 800,
width = 1200
)
# 초기 상태 스냅샷
app$expect_values()
# 지역 선택 변경
app$set_inputs(region = "부산")
# 변경 후 상태 스냅샷 — 처음 실행 시 기준값 저장
app$expect_values()
app$stop()
})
expect_values()는 현재 입력값, 출력값, 내보내기 값을 캡처해서 저장합니다. 이후 테스트를 다시 실행하면 저장된 기준값과 비교해 달라진 부분이 있으면 실패를 알립니다.
스냅샷 테스트
차트 이미지나 테이블 HTML을 비교하는 스냅샷 테스트를 추가합니다.
# 수정: modular-app/tests/testthat/test-app.R
library(shinytest2)
test_that("데이터 테이블 행 수 확인", {
app <- AppDriver$new(".", name = "table-test")
# 서울만 선택
app$set_inputs(region = "서울")
app$wait_for_idle(timeout = 5000)
# 테이블 출력값 가져오기
tbl_html <- app$get_value(output = "data_table")
# 테이블에 "서울"이 포함되었는지 확인
expect_true(grepl("서울", tbl_html))
app$stop()
})
test_that("연도 범위 변경 시 차트 타이틀 확인", {
app <- AppDriver$new(".", name = "year-test")
app$set_inputs(
region = "대구",
year_range = c(2020, 2022)
)
app$wait_for_idle()
# 현재 입력/출력 값 스냅샷
app$expect_values(
input = c("region", "year_range"),
output = "trend_chart"
)
app$stop()
})
expect_values() vs expect_screenshot()
| 방법 | 특징 | 적합한 경우 |
|---|---|---|
expect_values() |
입출력 JSON 비교 | 데이터 로직 검증 |
expect_screenshot() |
화면 이미지 비교 | UI 레이아웃 검증 |
expect_screenshot()은 운영체제별로 렌더링이 달라 불안정할 수 있습니다. 데이터 검증에는 expect_values()를 주로 씁니다.
테스트 실행
# RStudio 터미널 또는 콘솔에서
testthat::test_dir("tests/testthat")
# 또는 shinytest2 전용
shinytest2::test_app("modular-app")
처음 실행하면 스냅샷 파일(tests/testthat/_snaps/)이 생성됩니다. 두 번째 실행부터 기준값과 비교합니다.
스냅샷 업데이트
의도적으로 출력을 바꿨을 때는 기준 스냅샷을 갱신합니다.
# 스냅샷 강제 업데이트
testthat::snapshot_accept()
GitHub Actions와 연동
.github/workflows/shiny-test.yaml을 만들어 PR마다 자동 테스트를 실행합니다.
# 새 파일: .github/workflows/shiny-test.yamlname: Shiny App Testson: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: r-version: "4.3" - uses: r-lib/actions/setup-r-dependencies@v2 with: packages: | shiny shinytest2 bslib tidyverse plotly DT - name: Install Chromium run: chromote::install_chrome() shell: Rscript {0} - name: Run Tests run: shinytest2::test_app("modular-app") shell: Rscript {0}
이제 코드를 Push할 때마다 GitHub가 자동으로 앱을 실행하고 테스트 결과를 알려줍니다. 테스트가 통과해야만 머지할 수 있도록 설정하면 회귀 버그를 배포 전에 잡을 수 있습니다.