ARTEMIS Testing Strategy
Strahinja Stevanovic, Svetozar Nesic
2026-05-11
testing-strategy.RmdThis project uses testthat and devtools to
test the installed ARTEMIS package — including its R to Python to Cython
bridge logic.
Structure
tests/
├── testthat/
│ ├── helper.R # test utilities, env setup
│ ├── test-000-smoke.R # baseline smoke test
│ ├── test-100-bridge.R # bridge output consistency check
...
│ └── testthat.yml # optional (reporter config)
└── testthat.R # testthat entrypoint (required)
Test Workflow
ARTEMIS must be installed and its install location added to .libPaths() before running tests.
.libPaths(c("/path/to/ARTEMIS", .libPaths()))
library(devtools)
library(testthat)
library(ARTEMIS)
devtools::test(pkg = "/path/to/ARTEMIS/ARTEMIS")
Where last ARTEMIS refers to the actual package directory Parent dir might contain other build/test infra
What Gets Tested
- 000-smoke.R
Asserts testthat itself is wired and functional.
- 100-bridge.R
Uses reticulate::import_from_path() to load:
cython/main.py
python/main.py
Then checks their return values match using:
isTRUE(all.equal(r_cy, r_py, tolerance = 1e-8))
R↔︎Python Boundary
Scope (Exploration → Decisions)
1. Canonical interface (R side)
A single exported R function is the only supported entrypoint for
calling Python. All R code must go through this function instead of
calling reticulate directly.
Mocking strategy
The Python call can be replaced with a pure R mock so unit tests do not depend on reticulate or a Python runtime. Python and Cython are treated as interchangeable real implementations and are exercised only in integration tests.Serialization boundary
The interaction is treated as a strict serialization boundary: no Python objects cross into R, and all inputs and outputs are fully materialized R data structures.
Expected Output
devtools::test(pkg="/path/to/ARTEMIS")
ℹ Testing ARTEMIS
✔ | F W S OK | Context
✔ | 1 | 000-smoke
Processing patients: 100%|█| 1/1 [00:00<00:00, 1226.76
... [bridge output showing processed tables] ...
Cython module loaded successfully.
✔ | 1 | 100-bridge
══ Results ═══════════════════════════════════════════
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 2 ]
Warning message:
Objects listed as exports, but not present in
namespace:
• calculateEras
• combineOverlaps
...
Post-Test Cleanup
To fix the NAMESPACE export warning:
devtools::document()
This will regenerate the NAMESPACE file based on roxygen tags (#’ @export) in your R code.
Use devtools::check() When:
You’re releasing
You want full CRAN-style checks
You want to catch broken docs, missing imports, export issues, etc.
Use devtools::test() When:
You’re actively developing
You want fast, focused feedback
You’re iterating on R or Python bridge logic
Conventional Commits
ARTEMIS adopts the Conventional Commits
specification for all commit messages. The format is enforced locally by
the commit-msg hook and mandatorily by the
lint-commits CI job on every PR.
Format
<type>(<scope>): <short description>
[optional body]
[optional footer — BREAKING CHANGE: <description>]
Allowed types
| Type | When to use in ARTEMIS |
|---|---|
feat |
New alignment feature, new exported R function |
fix |
Bug in alignment, scoring, plotting, or I/O |
chore |
Dependency bumps, data updates, build scripts |
docs |
README, man/*.Rd, vignettes |
style |
Formatting only — no logic change |
refactor |
Code restructure with no behaviour change |
test |
testthat additions or fixes |
ci |
GitHub Actions workflow changes |
perf |
Algorithm performance improvements |
Scope vocabulary
r-bridge cython scoring preprocess plots
data hooks ci packaging
Scope is optional but encouraged. cython and similar
labels are purely descriptive — they do not imply the contributor needs
to touch Python/Cython.
Breaking changes
Append ! after the type, or add a
BREAKING CHANGE: footer:
feat(r-bridge)!: rename generateRawAlignments argument order
BREAKING CHANGE: the `df` parameter is now the second argument, not the first.
Worked examples
feat(cython): pass gap-open/extend params to C extension
fix(r-bridge): guard against empty alignment output
ci(docker): add arm64 image build workflow
chore(data): update regimen reference data 2025
perf(scoring): remove redundant max in TSW score matrix
docs(vignettes): add Gitflow branch topology section
test(r-bridge): add testthat case for zero-drug patient
refactor(preprocess): extract cleanText into its own function
What the commit-msg hook enforces (locally, opt-in)
The hook at .githooks/commit-msg rejects any message
that does not match:
^(feat|fix|chore|docs|style|refactor|test|ci|perf)(\(.+\))?: .+
It skips merge commits and rebase operations automatically.
Activate with:
Release workflow (cz bump)
The only person who needs commitizen installed locally is whoever
cuts the release. Everyone else: just git +
R.
pip install commitizen # once, on the release manager's machine
# On the release/* branch:
cz bump # reads commits since last tag, bumps DESCRIPTION Version,
# updates CHANGELOG.md, creates a git tag (e.g. v1.5.0)
git push && git push --tagscz bump rewrites the Version: field in
DESCRIPTION according to semver (patch / minor / major
determined by commit types — feat → minor,
fix/perf → patch, BREAKING CHANGE
→ major).
The tag format is v$version (e.g. v1.5.0),
configured in .cz.toml.
How commit validation works
.cz.toml is the single source of truth for commit rules
and versioning:
-
cz check(run by CI) reads allowed types from.cz.toml -
cz bumpreads the commit log and decides the next version - The
commit-msghook uses the same type list embedded as a regex
Contributors never need to read .cz.toml directly — the
type list above and the examples in this section are sufficient.