Testing a new model
Every supported model needs the same battery of tests. The list below exists because coverage had drifted by more than an order of magnitude between model files (64 lines for rq against 2392 for lightgbm), and the thin files were where real bugs survived: an xgboost round-trip that compared tidypredict against itself, and a tidypredict_test() method for ranger that could never fail.
Tests for R/model-{name}.R go in tests/testthat/test-model-{name}.R. Work through this list:
-
parse_model()structure. Checkgeneral$model,general$type, andgeneral$versionare what you expect, plus whatever the builder relies on. -
tidypredict_fit()output. Snapshot the generated expression withround_print()so the snapshot does not depend on floating point noise or the platform. -
Numeric agreement against the package’s own
predict(). This is the assertion that matters. Evaluate the fitted formula withrlang::eval_tidy()and compare against the model package’spredict(), never against another tidypredict result. -
tidypredict_test(). Assert directly on the result (expect_false(result$alert)), rather than wrapping it inexpect_snapshot(). A numeric regression should read as a failure, not as a snapshot diff. -
tidypredict_to_column(). At least one test that the column actually lands in the data frame. -
tidypredict_sql(). Confirm the formula survives translation. -
YAML round-trip.
parse_model(), write, read,as_parsed_model(), then compare against the model’s ownpredict(). Comparing the reloaded parsed model against the un-serialized one only proves serialization is lossless: a parser that is wrong the same way on both sides passes. - Print snapshot, if the model has bespoke print output.
- Unsupported configurations. Snapshot the error for anything the parser rejects (a family, an objective, a classification mode).
- Factor predictors, if the model accepts them.
-
NAhandling. Predict on data that actually containsNA, not just a structural check that a missing-value branch was generated.
Conventions
- Use
skip_if_not_installed("pkg")in every test that needs a suggested package, rather thanskip_on_cran(). - Use
withr::local_tempfile(fileext = ".yml")for temp files, so they are cleaned up. - No
library()calls. Namespace everything. - Prefer a direct assertion for correctness and reserve snapshots for printed output and error messages.
Comparing against the fitted model, not against tidypredict
The single most common way a test here has been wrong is comparing two tidypredict outputs to each other. tidypredict_fit(pm) against tidypredict_fit(pm_loaded), or a parsed model against a fitted one, is a real assertion but a weak one: it cannot catch a parser that is consistently wrong. Every model needs at least one test whose right-hand side comes from the modelling package itself.
