The validity of the {ramnog} framework is ensured on several levels:
- Unit test individual functions/modules in all packages
- Integration/end-to-end testing to ensure the interactions between individual functions/modules (that have been unit tested) are as expected
- CI/CD to ensure changes are properly tested and deployed to the users
Unit testing
Each package contains the unit tests for the functionality that package is responsible for. This means, for example, it is the responsibility of the unit-tests in {chefStats} to ensure that the statistical functions written in chefStats produce the result they are supposed to produce.
Integration testing
Integration testing verifies the framework produces the expected results
The integration testing is done via the {testthat} framework, and are essentially unit-tests that cover many different units
The goal is to compare the results of an analysis run via ramnog with the expected results done from a manual calculation of the same analysis.
For example, below is the unit test to ensure the results of an endpoint definition defined and run via chef match those calculated manually
Example of integration test using {testthat} framework
testthat::test_that("Valide stats when one strata level is not found",
{
# SETUP -------------------------------------------------------------------
testr::create_local_project()
mk_adae <- function(study_metadata) {
adsl <- pharmaverseadam::adsl |> data.table::setDT()
adae <- pharmaverseadam::adae |> data.table::setDT()
adae_out <-
merge(adsl, adae[, c(setdiff(names(adae), names(adsl)), "USUBJID"), with =
F], by = "USUBJID", all = TRUE)
adae_out[TRT01A %in% c('Placebo', 'Xanomeline High Dose'),]
}
mk_ep_def <- function() {
chef::mk_endpoint_str(
study_metadata = list(),
pop_var = "SAFFL",
pop_value = "Y",
treatment_var = "TRT01A",
treatment_refval = "Xanomeline High Dose",
stratify_by = list(c("SEX")),
data_prepare = mk_adae,
endpoint_label = "A",
custom_pop_filter = "SEX == 'F'",
group_by = list(list(AESEV = c())),
stat_by_strata_by_trt = list(
"N" = chefStats::n_subj,
"E" = chefStats::n_event,
"N subj E" = chefStats::n_subj_event
),
stat_by_strata_across_trt = list("RR" = chefStats::RR,
"OR" = chefStats::OR),
stat_across_strata_across_trt = list("P-interaction" = chefStats::p_val_interaction)
)
}
chef::use_chef(
pipeline_dir = "pipeline",
r_functions_dir = "R/",
pipeline_id = "01",
mk_endpoint_def_fn = mk_ep_def,
mk_adam_fn = list(mk_adae)
)
# ACT ---------------------------------------------------------------------
targets::tar_make()
targets::tar_load(ep_stat)
# EXPECT ------------------------------------------------------------------
# Manually calculated expected results
x <- mk_adae(study_metadata = NULL)
# Relative Risk
actual <-
ep_stat[endpoint_group_filter == "AESEV == \"SEVERE\"" &
fn_name == "RR" &
stat_filter == "SEX == \"F\"" & stat_result_label == "RR", stat_result_value]
x1 <- x[SEX == "F" & SAFFL == "Y"]
x1[, event := FALSE]
x1[AESEV == "SEVERE", event := TRUE] |> data.table::setorder(-event)
x1_unique <-
unique(x1, by = "USUBJID", fromLast = FALSE)
two_by_two <-
x1_unique[, .N, by = .(TRT01A, event)][order(TRT01A, event)]
# Outcome, exposed
a <-
two_by_two[TRT01A == "Xanomeline High Dose" &
(event), N]
# No outcome, exposed
b <-
two_by_two[TRT01A == "Xanomeline High Dose" &
!(event), N]
# Outcome, not exposed
c <-
two_by_two[TRT01A == "Placebo" &
(event), N]
# No outcome, not exposed
d <-
two_by_two[TRT01A == "Placebo" &
!(event), N]
expected <- (a / sum(a, b)) / (c / sum(c, d))
expect_identical(actual, expected)
# Number of Events
actual <-
ep_stat[endpoint_group_filter == "AESEV == \"SEVERE\"" &
fn_name == "E" &
strata_var == "SEX"]
expected <-
x1[(event), .N, by = .(TRT01A)][order(TRT01A)][, as.double(N)]
expect_identical(actual$stat_result_value, expected)
})
#> v Setting active project to '/tmp/Rtmp2Qer9r/testproj1c8318edb087'
#> v Writing 'R/packages.R'
#> v Writing 'pipeline/pipeline_01.R'
#> * Edit 'pipeline/pipeline_01.R'
#> * Edit 'R/mk_adae.R'
#> i Renaming "mk_ep_def" to "mk_endpoint_def.R"
#>
#> * Edit 'R/mk_endpoint_def.R'
#> Loading required package: targets
#> ▶ dispatched target ep
#> ● completed target ep [0.03 seconds]
#> ▶ dispatched target ep_id
#> ● completed target ep_id [0.009 seconds]
#> ▶ dispatched branch ep_fn_map_a9f94f07
#> ● completed branch ep_fn_map_a9f94f07 [0.028 seconds]
#> ● completed pattern ep_fn_map
#> ▶ dispatched target user_def_fn
#> ● completed target user_def_fn [0.004 seconds]
#> ▶ dispatched target study_data
#> ● completed target study_data [0.04 seconds]
#> ▶ dispatched target fn_map_tibble
#> ● completed target fn_map_tibble [0.004 seconds]
#> ▶ dispatched branch ep_and_data_a9f94f07
#> ● completed branch ep_and_data_a9f94f07 [0.009 seconds]
#> ● completed pattern ep_and_data
#> ▶ dispatched branch fn_map_9ec9e198
#> ● completed branch fn_map_9ec9e198 [0 seconds]
#> ● completed pattern fn_map
#> ▶ dispatched branch analysis_data_container_5d792ea5
#> ● completed branch analysis_data_container_5d792ea5 [0 seconds]
#> ● completed pattern analysis_data_container
#> ▶ dispatched branch ep_with_data_key_5d792ea5
#> ● completed branch ep_with_data_key_5d792ea5 [0.001 seconds]
#> ● completed pattern ep_with_data_key
#> ▶ dispatched branch ep_expanded_0ccac0a6
#> ● completed branch ep_expanded_0ccac0a6 [0.037 seconds]
#> ● completed pattern ep_expanded
#> ▶ dispatched branch ep_event_index_74b91187
#> ● completed branch ep_event_index_74b91187 [0.006 seconds]
#> ● completed pattern ep_event_index
#> ▶ dispatched branch ep_crit_endpoint_653759aa
#> ● completed branch ep_crit_endpoint_653759aa [0.004 seconds]
#> ● completed pattern ep_crit_endpoint
#> ▶ dispatched branch ep_crit_by_strata_by_trt_8f632703
#> ● completed branch ep_crit_by_strata_by_trt_8f632703 [0.009 seconds]
#> ● completed pattern ep_crit_by_strata_by_trt
#> ▶ dispatched branch ep_crit_by_strata_across_trt_06f096b7
#> ● completed branch ep_crit_by_strata_across_trt_06f096b7 [0.004 seconds]
#> ● completed pattern ep_crit_by_strata_across_trt
#> ▶ dispatched branch ep_prep_by_strata_across_trt_ac5fea46
#> ● completed branch ep_prep_by_strata_across_trt_ac5fea46 [0.039 seconds]
#> ● completed pattern ep_prep_by_strata_across_trt
#> ▶ dispatched branch ep_prep_across_strata_across_trt_ac5fea46
#> ● completed branch ep_prep_across_strata_across_trt_ac5fea46 [0.013 seconds]
#> ● completed pattern ep_prep_across_strata_across_trt
#> ▶ dispatched branch ep_prep_by_strata_by_trt_ac5fea46
#> ● completed branch ep_prep_by_strata_by_trt_ac5fea46 [0.063 seconds]
#> ● completed pattern ep_prep_by_strata_by_trt
#> ▶ dispatched target ep_rejected
#> ● completed target ep_rejected [0 seconds]
#> ▶ dispatched branch ep_stat_by_strata_across_trt_db74539d
#> ● completed branch ep_stat_by_strata_across_trt_db74539d [0.125 seconds]
#> ● completed pattern ep_stat_by_strata_across_trt
#> ▶ dispatched branch ep_stat_across_strata_across_trt_091f4560
#> ● completed branch ep_stat_across_strata_across_trt_091f4560 [0.032 seconds]
#> ● completed pattern ep_stat_across_strata_across_trt
#> ▶ dispatched branch ep_stat_by_strata_by_trt_f99e4db4
#> ● completed branch ep_stat_by_strata_by_trt_f99e4db4 [0.042 seconds]
#> ● completed pattern ep_stat_by_strata_by_trt
#> ▶ dispatched target ep_stat_nested
#> ● completed target ep_stat_nested [0 seconds]
#> ▶ dispatched target ep_stat
#> ● completed target ep_stat [0.013 seconds]
#> ▶ ended pipeline [0.839 seconds]
#> v Restoring original working directory: '/home/runner/work/ramnog/ramnog/vignettes/'
#> v Setting active project to '<no active project>'
#> v Deleting temporary project: '/tmp/Rtmp2Qer9r/testproj1c8318edb087/'
#> Test passed
CI/CD
Please see the DevOps vignette for details on the CI/CD infrastructure.