Appendix C — Procedure Behavior Tests

This document provides unit tests for the LogoClim NetLogo model. The tests validate procedure behavior to ensure correct functionality.

C.1 Problem

NetLogo procedures can produce unexpected results and exhibit side effects that may go undetected during development. The unit tests in this document validate procedure behavior through expectation tests, ensuring they function as intended and produce reliable results.

C.2 Methods

C.2.1 Data munging

All processes were made using the Quarto publishing system (Allaire et al., n.d.), the NetLogo environment, the R programming language (R Core Team, n.d.), and several R packages.

For data manipulation and workflow, priority was given to packages from the Tidyverse, rOpenSci and rspatial ecosystems, as well as other packages adhering to the tidy tools manifesto (Wickham, 2023).

C.2.2 NetLogo Integration

Integration with NetLogo (Wilensky, 1999) is facilitated by the logolink R package (Vartanian, 2026b). This package enables the execution of BehaviorSpace experiments directly from R.

Output is extracted in Table or Lists format, depending on the experiment configuration.

No Java dependencies are required. NetLogo bundles its own Java Runtime Environment (JRE), ensuring independent operation regardless of the system’s Java installation.

C.2.3 Continuous Integration

These tests use the latest release of NetLogo and are automated using GitHub Actions provided by the LogoActions project (Vartanian, 2026a). Each commit to the code repository triggers test execution, ensuring that errors are caught early in the development process.

C.2.4 Expectation Tests

Expectations are validated using the testthat (Wickham, 2011) and checkmate (Lang, 2017) R packages.

C.2.5 Code Style

The Tidyverse Tidy Tools Manifesto (Wickham, 2023), code style guide (Wickham, n.d.-a) and design principles (Wickham, n.d.-b) were followed to ensure consistency and enhance readability.

C.2.6 Reproducibility

The pipeline is fully reproducible and can be run again at any time. To ensure consistent results, the renv package (Ushey & Wickham, 2025) was used to manage and restore the R environment. See the README file in the code repository to learn how to run it.

C.3 Set the Environment

C.3.1 Load Packages

C.3.2 Set Initial Variables

Setting the JAVA_TOOL_OPTIONS is optional, but recommended to avoid unnecessary messages from the Java Media Framework.

Sys.setenv(JAVA_TOOL_OPTIONS = "-Dcom.sun.media.jai.disableMediaLib=true")
model_path <- here("nlogox", "logoclim.nlogox")

C.4 as-list

setup_file <- create_experiment(
  name = "as-list",
  setup = "setup false",
  go = NULL,
  metrics = c(
    'as-list "a"',
    'as-list 1',
    'as-list true',
    'as-list [1 2 3]'
  )
)
results <-
  model_path |>
  run_experiment(
    setup_file = setup_file
  )
#> ℹ Running model
#> ✔ Running model [5.4s]
#> 
#> ℹ Gathering metadata
#> ✔ Gathering metadata [23ms]
#> 
#> ℹ Processing table output
#> ✔ Processing table output [15ms]
#> 
#> ℹ The experiment run produced the following messages:
#> 
#> Picked up JAVA_TOOL_OPTIONS: -Dcom.sun.media.jai.disableMediaLib=true
results |>
  pluck("table") |>
  glimpse()
#> Rows: 1
#> Columns: 6
#> $ run_number    <dbl> 1
#> $ step          <dbl> 1
#> $ as_list_a     <chr> "[a]"
#> $ as_list_1     <chr> "[1]"
#> $ as_list_true  <chr> "[true]"
#> $ as_list_1_2_3 <chr> "[1 2 3]"
test_that("as-list", {
  results |>
    extract2("table") |>
    pull(as_list_a) |>
    expect_equal("[a]")

  results |>
    extract2("table") |>
    pull(as_list_1) |>
    expect_equal("[1]")

  results |>
    extract2("table") |>
    pull(as_list_true) |>
    expect_equal("[true]")

  results |>
    extract2("table") |>
    pull(as_list_1_2_3) |>
    expect_equal("[1 2 3]")
})
#> Test passed with 4 successes 🎊.

C.5 quartile

setup_file <- create_experiment(
  name = "quartile",
  setup = "setup false",
  go = NULL,
  metrics = c(
    'quartile [0 1 2 3 4] 0',
    'quartile [0 1 2 3 4] 1',
    'quartile [0 1 2 3 4] 2',
    'quartile [0 1 2 3 4] 3',
    'quartile [0 1 2 3 4] 4',
    'quartile [0 1 2 3 4] "iqr"',
    'quartile [0 1 2 3 4] "length"',
    'quartile [0 1 2 3 4 5] 0',
    'quartile [0 1 2 3 4 5] 1',
    'quartile [0 1 2 3 4 5] 2',
    'quartile [0 1 2 3 4 5] 3',
    'quartile [0 1 2 3 4 5] 4',
    'quartile [0 1 2 3 4 5] "iqr"',
    'quartile [0 1 2 3 4 5] "length"'
  )
)
results <-
  model_path |>
  run_experiment(
    setup_file = setup_file
  )
#> ℹ Running model
#> ✔ Running model [5.6s]
#> 
#> ℹ Gathering metadata
#> ✔ Gathering metadata [19ms]
#> 
#> ℹ Processing table output
#> ✔ Processing table output [9ms]
#> 
#> ℹ The experiment run produced the following messages:
#> 
#> Picked up JAVA_TOOL_OPTIONS: -Dcom.sun.media.jai.disableMediaLib=true
results |>
  pluck("table") |>
  glimpse()
#> Rows: 1
#> Columns: 16
#> $ run_number                  <dbl> 1
#> $ step                        <dbl> 1
#> $ quartile_0_1_2_3_4_0        <dbl> 0
#> $ quartile_0_1_2_3_4_1        <dbl> 0
#> $ quartile_0_1_2_3_4_2        <dbl> 1
#> $ quartile_0_1_2_3_4_3        <dbl> 2
#> $ quartile_0_1_2_3_4_4        <dbl> 4
#> $ quartile_0_1_2_3_4_iqr      <dbl> 2
#> $ quartile_0_1_2_3_4_length   <dbl> 1.25
#> $ quartile_0_1_2_3_4_5_0      <dbl> 0
#> $ quartile_0_1_2_3_4_5_1      <dbl> 0
#> $ quartile_0_1_2_3_4_5_2      <dbl> 2
#> $ quartile_0_1_2_3_4_5_3      <dbl> 3
#> $ quartile_0_1_2_3_4_5_4      <dbl> 5
#> $ quartile_0_1_2_3_4_5_iqr    <dbl> 3
#> $ quartile_0_1_2_3_4_5_length <dbl> 1.5
test_that("quartile [0 1 2 3 4]", {
  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_0) |>
    expect_equal(0)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_1) |>
    expect_equal(0)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_2) |>
    expect_equal(1)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_3) |>
    expect_equal(2)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_4) |>
    expect_equal(4)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_iqr) |>
    expect_equal(2 - 0)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_length) |>
    expect_equal(5 / 4)
})
#> Test passed with 7 successes 🌈.
test_that("quartile [0 1 2 3 4 5]", {
  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_0) |>
    expect_equal(0)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_1) |>
    expect_equal(0)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_2) |>
    expect_equal(2)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_3) |>
    expect_equal(3)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_4) |>
    expect_equal(5)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_iqr) |>
    expect_equal(3 - 0)

  results |>
    extract2("table") |>
    pull(quartile_0_1_2_3_4_5_length) |>
    expect_equal(6 / 4)
})
#> Test passed with 7 successes 🎉.

C.6 single-quote

setup_file <- create_experiment(
  name = "single-quote",
  setup = "setup false",
  go = NULL,
  metrics = c(
    'single-quote 1',
    'single-quote [1 2 3]'
  )
)
results <-
  model_path |>
  run_experiment(
    setup_file = setup_file
  )
#> ℹ Running model
#> ✔ Running model [5.4s]
#> 
#> ℹ Gathering metadata
#> ✔ Gathering metadata [9ms]
#> 
#> ℹ Processing table output
#> ✔ Processing table output [8ms]
#> 
#> ℹ The experiment run produced the following messages:
#> 
#> Picked up JAVA_TOOL_OPTIONS: -Dcom.sun.media.jai.disableMediaLib=true
results |>
  pluck("table") |>
  glimpse()
#> Rows: 1
#> Columns: 4
#> $ run_number         <dbl> 1
#> $ step               <dbl> 1
#> $ single_quote_1     <chr> "'1'"
#> $ single_quote_1_2_3 <chr> "['1' '2' '3']"
test_that("single-quote", {
  results |>
    extract2("table") |>
    pull(single_quote_1) |>
    expect_equal("'1'")

  results |>
    extract2("table") |>
    pull(single_quote_1_2_3) |>
    expect_equal("['1' '2' '3']")
})
#> Test passed with 2 successes 😀.

C.7 str-to-num-month

setup_file <- create_experiment(
  name = "str-to-num-month",
  setup = "setup false",
  go = NULL,
  metrics = c(
    'str-to-num-month "January"',
    'str-to-num-month "July"',
    'str-to-num-month "December"'
  )
)
results <-
  model_path |>
  run_experiment(
    setup_file = setup_file
  )
#> ℹ Running model
#> ✔ Running model [5.5s]
#> 
#> ℹ Gathering metadata
#> ✔ Gathering metadata [9ms]
#> 
#> ℹ Processing table output
#> ✔ Processing table output [8ms]
#> 
#> ℹ The experiment run produced the following messages:
#> 
#> Picked up JAVA_TOOL_OPTIONS: -Dcom.sun.media.jai.disableMediaLib=true
results |>
  pluck("table") |>
  glimpse()
#> Rows: 1
#> Columns: 5
#> $ run_number                <dbl> 1
#> $ step                      <dbl> 1
#> $ str_to_num_month_january  <dbl> 1
#> $ str_to_num_month_july     <dbl> 7
#> $ str_to_num_month_december <dbl> 12
test_that("str-to-num-month", {
  results |>
    extract2("table") |>
    pull(str_to_num_month_january) |>
    expect_equal(1)

  results |>
    extract2("table") |>
    pull(str_to_num_month_july) |>
    expect_equal(7)

  results |>
    extract2("table") |>
    pull(str_to_num_month_december) |>
    expect_equal(12)
})
#> Test passed with 3 successes 🥳.

C.8 unique-outliers

setup_file <- create_experiment(
  name = "unique-outliers",
  setup = "setup false",
  go = NULL,
  metrics = c(
    'unique-outliers [1 2 3 4 100 100 500 500] 1.5',
    'unique-outliers [1 2 3] 3'
  )
)
results <-
  model_path |>
  run_experiment(
    setup_file = setup_file
  )
#> ℹ Running model
#> ✔ Running model [5.4s]
#> 
#> ℹ Gathering metadata
#> ✔ Gathering metadata [9ms]
#> 
#> ℹ Processing table output
#> ✔ Processing table output [8ms]
#> 
#> ℹ The experiment run produced the following messages:
#> 
#> Picked up JAVA_TOOL_OPTIONS: -Dcom.sun.media.jai.disableMediaLib=true
results |>
  pluck("table") |>
  glimpse()
#> Rows: 1
#> Columns: 4
#> $ run_number                                  <dbl> 1
#> $ step                                        <dbl> 1
#> $ unique_outliers_1_2_3_4_100_100_500_500_1_5 <chr> "[100 500]"
#> $ unique_outliers_1_2_3_3                     <chr> "[]"
test_that("unique-outliers", {
  results |>
    extract2("table") |>
    pull(unique_outliers_1_2_3_4_100_100_500_500_1_5) |>
    expect_equal("[100 500]")

  results |>
    extract2("table") |>
    pull(unique_outliers_1_2_3_3) |>
    expect_equal("[]")
})
#> Test passed with 2 successes 🥳.