diff --git a/.github/workflows/testthat-module.yaml b/.github/workflows/testthat-module.yaml new file mode 100644 index 0000000..bff6e71 --- /dev/null +++ b/.github/workflows/testthat-module.yaml @@ -0,0 +1,25 @@ +## Example caller for the testthat-module reusable workflow. +## Replace fireSense_dataPrepFit with the module name (which must match the repository name). + +name: testthat-module + +on: + pull_request: + branches: [main, master, development] + push: + branches: [main, master, development] + paths: + - .github/workflows/testthat-module.yaml + - fireSense_dataPrepFit.R + - R/** + - tests/** + workflow_dispatch: + +jobs: + testthat-module: + ## No permissions block: unlike render-module-rmd, this workflow writes nothing + ## back to the repository, so the default `contents: read` it declares for + ## itself is all it needs. + uses: PredictiveEcology/actions/.github/workflows/testthat-module.yaml@main + with: + module: fireSense_dataPrepFit diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 0000000..e351143 --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1,50 @@ +## Template `tests/testthat/setup.R` for a SpaDES module. +## +## The module is converted to a package rendition before the tests run (see the +## testthat-module workflow), so the module's own functions are already in a real +## namespace: a test can call `myHelper(x)` directly rather than reaching into +## `sim@.xData$.mods$$myHelper`. +## +## This file only sets options and makes a scratch directory. It deliberately does +## NOT install packages or download anything: dependency resolution happens once, +## before the tests, from the module's own `reqdPkgs` metadata. + +withr::local_options( + list( + reproducible.useMemoise = TRUE, + reproducible.verbose = -2, + Require.verbose = -2, + spades.moduleCodeChecks = FALSE, + spades.moduleDocument = FALSE, + spades.useRequire = FALSE + ), + .local_envir = testthat::teardown_env() +) + +## Where the module is, from testthat's working directory. +## +## testthat runs with the working directory set to `tests/testthat`, so the module +## directory is two levels up and the `modulePath` that SpaDES.core wants -- the +## directory *containing* modules -- is three. Getting this wrong is silent: the +## module is simply not found, and every test that needs it skips or errors on +## something unrelated. +moduleRoot <- normalizePath(file.path("..", ".."), winslash = "/", mustWork = TRUE) +moduleName <- basename(moduleRoot) +modulePath <- dirname(moduleRoot) + +## A scratch tree removed when the suite finishes. Write here, never beside the +## module: the tests run against a throwaway copy, but the habit matters when they +## are run by hand. +testPaths <- local({ + root <- withr::local_tempdir(.local_envir = testthat::teardown_env()) + paths <- list( + cachePath = file.path(root, "cache"), + inputPath = file.path(root, "inputs"), + modulePath = modulePath, + outputPath = file.path(root, "outputs") + ) + for (p in paths[c("cachePath", "inputPath", "outputPath")]) { + dir.create(p, recursive = TRUE, showWarnings = FALSE) + } + paths +}) diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R new file mode 100644 index 0000000..7b5fb40 --- /dev/null +++ b/tests/testthat/test-metadata.R @@ -0,0 +1,100 @@ +## The module's metadata is its public contract: a project using this module binds +## to these object names and classes. Renaming or retyping one breaks every caller, +## which is exactly the class of change the raster -> terra migration makes, so it is +## worth asserting here rather than discovering downstream. +## +## When a change is deliberate, update this file in the same commit and bump the +## module version to match: removed, renamed or retyped is a MAJOR bump. + +test_that("module metadata parses", { + md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) + expect_type(md, "list") + expect_identical(md$name, moduleName) +}) + +test_that("inputs are the expected names and classes", { + md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) + inputs <- stats::setNames(md$inputObjects$objectClass, md$inputObjects$objectName) + expect_identical( + inputs[order(names(inputs))], + c(climateVariablesForFire = "list", + cohortDatas = "list", + firePolys = "list", + firePolysForAge = "list", + historicalClimateRasters = "list", + historicalFireRaster = "SpatRaster", + ignitionFirePoints = "sf", + missingLCCgroup = "character", + nonForestedLCCGroups = "list", + pixelGroupMaps = "list", + propFlammables = "list", + rasterToMatch = "SpatRaster", + rasterToMatch_biomassParam = "SpatRaster", + rstLCCs = "list", + sppEquiv = "data.table", + spreadFirePoints = "list", + spreadFirePolys = "list", + spreadFitAdditionalColNames = "character", + standAgeMaps = "list", + studyArea = "SpatVector", + studyArea_biomassParam = "SpatVector", + studyAreaReporting = "sf") + ) +}) + +test_that("outputs are the expected names and classes", { + md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) + outputs <- stats::setNames(md$outputObjects$objectClass, md$outputObjects$objectName) + expect_identical( + outputs[order(names(outputs))], + c(climateVariables = "list", + climateVariablesForFire = "list", + fireBufferedListDT = "list", + fireSense_annualSpreadFitCovariates = "list", + fireSense_escapeCovariates = "data.table", + fireSense_escapeFormula = "character", + fireSense_ignitionCovariates = "data.table", + fireSense_ignitionFormula = "character", + fireSense_nonAnnualSpreadFitCovariates = "list", + fireSense_spreadFormula = "character", + flammableRTM = "SpatRaster", + flammableRTMs = "list", + fuelClassTable = "data.table", + ignitionFirePoints = "sf", + ignitionFitRTM = "SpatRaster", + landcoverDT = "data.table", + landcoverDTs = "list", + lightningMaps = "SpatRaster", + missingLCCgroup = "character", + nonForest_timeSinceDisturbance = "SpatRaster", + nonForest_timeSinceDisturbances = "list", + nonForestedLCCGroups = "list", + propFlammable = "SpatRaster", + rstLCC = "SpatRaster", + rstLCC_RTM = "SpatRaster", + rstLCCs = "list", + sppColorVect = "character", + sppEquiv = "data.table", + sppNameVector = "character", + spreadFirePoints = "list", + spreadFirePolys = "list", + spreadFitPreRun = "data.frame", + standAgeMap = "SpatRaster", + studyAreaWithSpreadParams = "sf") + ) +}) + +test_that("parameters are the expected names", { + md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) + expect_identical( + sort(md$parameters$paramName), + sort(c(".plotInterval", ".saveInitialTime", ".saveInterval", ".studyAreaName", + ".useCache", "areaMultiplier", "bufferForFireRaster", "cutoffForYoungAge", + "dataYears", "estimateFuelClasses", "fireYears", "flammabilityThreshold", + "forestedLCC", "fuelClassCol", "igAggFactor", "igFocalFactor", + "minBufferSize", "modelAlgorithm", "nonflammableLCC", + "nonForestCanBeYoungAge", "sppEquivCol", "spreadFitFilename", + "spreadFitGoogleDriveFolder", "targetFuelClasses", "useCentroids", + "useRasterizedFireForSpread", "whichModulesToPrepare")) + ) +}) diff --git a/tests/testthat/test-template.R b/tests/testthat/test-template.R deleted file mode 100644 index 5aa237a..0000000 --- a/tests/testthat/test-template.R +++ /dev/null @@ -1,74 +0,0 @@ -## Please do three things to ensure this template is correctly modified: -## -## 1. Rename this file based on the content you are testing using `test-functionName.R` format -## so that your can directly call `moduleCoverage` to calculate module coverage information. -## `functionName` is a function's name in your module (e.g., `", name, "Event1`). -## -## 2. Copy this file to the tests folder (i.e., `tests/testthat/`). -## -## 3. Modify the test description based on the content you are testing. -## -test_that("test Event1 and Event2.", { - module <- list("fireSense_dataPrepFit") - path <- list( - modulePath = "C:/Ian/Git", - outputPath = file.path(tempdir(), "outputs") - ) - parameters <- list( - #.progress = list(type = graphical, interval = 1), - .globals = list(verbose = FALSE), - fireSense_dataPrepFit = list(.saveInitialTime = NA) - ) - times <- list(start = 0, end = 1) - - # If your test function contains `time(sim)`, you can test the function at a - # particular simulation time by defining the start time above. - object1 <- object1 # please specify - object2 <- object2 # please specify - objects <- list(object1 = object1, object2 = object2) - - mySim <- simInit(times = times, - params = parameters, - modules = module, - objects = objects, - paths = path) - - # You may need to set the random seed if your module or its functions use the - # random number generator. - set.seed(1234) - - # You have two strategies to test your module: - # 1. Test the overall simulation results for the given objects, using the - # sample code below: - - output <- spades(mySim, debug = FALSE) - - # is output a simList? - expect_is(output, "simList") - - # does output have your module in it - expect_true(any(unlist(modules(output)) %in% c(unlist(module)))) - - # did it run to the end? - expect_true(time(output) == 1) - - # 2. Test the functions inside of the module using the sample code below: - # To allow the `moduleCoverage` function to calculate unit test coverage - # level, it needs access to all functions directly. - # Use this approach when using any function within the simList object - # (i.e., one version as a direct call, and one with `simList` object prepended). - - simOutput <- Event1(mySim) - - expectedOutputEvent1Test1 <- "this is test for event 1." # please define your expection of your output - expect_is(class(simOutput$event1Test1), "character") - expect_equal(simOutput$event1Test1, expectedOutputEvent1Test1) # or other expect function in testthat package. - expect_equal(simOutput$event1Test2, as.numeric(999)) # or other expect function in testthat package. - - simOutput <- Event2(mySim) - - expectedOutputEvent2Test1 <- "this is test for event 2." # please define your expection of your output - expect_is(class(simOutput$event2Test1), character) - expect_equal(simOutput$event2Test1, expectedOutputEvent2Test1) # or other expect function in testthat package. - expect_equal(simOutput$event2Test2, as.numeric(777)) # or other expect function in testthat package. -}) diff --git a/tests/unitTests.R b/tests/unitTests.R deleted file mode 100644 index 84fd5ed..0000000 --- a/tests/unitTests.R +++ /dev/null @@ -1,8 +0,0 @@ -# Please build your own test file from test-template.R, and place it in tests folder -# please specify the package you need to run the sim function in the test files. - -# to test all the test files in the tests folder: -testthat::test_dir(file.path("tests", "testthat")) - -# Alternative, you can use test_file to test individual test file, e.g.: -testthat::test_file(file.path("tests", "testthat", "test-template.R"))