library(testthat) if (requireNamespace("hedgehog", quietly = TRUE)) { library(hedgehog) } else { message("hedgehog package not available, skipping property-based tests") quit(save = "no", status = 0) } library(ausoa) context("Property-Based Tests") # Property: Simulation results should be deterministic with same seed test_that("Simulation is deterministic with fixed seed", { forall( gen.element(c(123, 456, 789, 101112)), # Test different seeds function(seed) { set.seed(seed) result1 <- simulation_cycle_fcn( population_data = 100, time_horizon = 5, scenario = "base_case" ) set.seed(seed) result2 <- simulation_cycle_fcn( population_data = 100, time_horizon = 5, scenario = "base_case" ) expect_equal(result1, result2) } ) }) # Property: Population size should scale linearly with input test_that("Population scaling is approximately linear", { forall( gen.element(c(50, 100, 200, 500)), # Different population sizes function(pop_size) { set.seed(123) result <- simulation_cycle_fcn( population_data = pop_size, time_horizon = 3, scenario = "base_case" ) # Results should scale roughly with population size # Allow for some stochastic variation (20%) expected_scale <- pop_size / 100 actual_scale <- length(result) / 100 # Assuming result length relates to population expect_true(abs(actual_scale - expected_scale) / expected_scale < 0.2) } ) }) # Property: Time horizon should not affect initial results test_that("Time horizon doesn't affect initial time steps", { forall( gen.element(c(5, 10, 15, 20)), # Different time horizons function(time_horizon) { set.seed(123) result <- simulation_cycle_fcn( population_data = 100, time_horizon = time_horizon, scenario = "base_case" ) # First few time steps should be consistent regardless of total horizon # This tests that the simulation logic is time-horizon independent for early steps expect_true(length(result) > 0) expect_true(is.list(result) || is.data.frame(result) || is.numeric(result)) } ) }) # Property: Invalid inputs should fail gracefully test_that("Invalid inputs are handled properly", { # Test negative population expect_error( simulation_cycle_fcn( population_data = -100, time_horizon = 5, scenario = "base_case" ) ) # Test zero time horizon expect_error( simulation_cycle_fcn( population_data = 100, time_horizon = 0, scenario = "base_case" ) ) # Test invalid scenario expect_error( simulation_cycle_fcn( population_data = 100, time_horizon = 5, scenario = "invalid_scenario" ) ) }) # Property: Edge cases for numeric inputs test_that("Edge case numeric inputs are handled", { forall( gen.element(c(1, 2, 1000, 10000)), # Edge case population sizes function(pop_size) { result <- simulation_cycle_fcn( population_data = pop_size, time_horizon = 3, scenario = "base_case" ) # Should not crash and should return some result expect_true(!is.null(result)) expect_true(length(result) > 0) } ) }) # Property: Configuration changes should affect results appropriately test_that("Configuration changes produce different results", { set.seed(123) result1 <- simulation_cycle_fcn( population_data = 100, time_horizon = 5, scenario = "base_case" ) set.seed(123) result2 <- simulation_cycle_fcn( population_data = 100, time_horizon = 5, scenario = "intervention" # Different scenario ) # Different scenarios should produce different results expect_false(identical(result1, result2)) }) # Property: Memory usage should be reasonable test_that("Memory usage scales appropriately", { forall( gen.element(c(50, 100, 200)), # Different sizes function(pop_size) { # Track memory before mem_before <- gc() result <- simulation_cycle_fcn( population_data = pop_size, time_horizon = 3, scenario = "base_case" ) # Track memory after mem_after <- gc() # Memory increase should be reasonable (less than 500MB) mem_increase <- sum(mem_after[, 2] - mem_before[, 2]) expect_lt(mem_increase, 500 * 1024 * 1024) # 500MB in bytes } ) })