Testing conventions
Layout
Tests mirror the source tree, one directory per sub-module, aggregated by test/runtests.jl:
test/
Elliptic/ Core/ Elasticity/ Cracks/ Conductivity/
LayeredSpheres/ LayeredSpheroids/ Schemes/ Viscoelasticity/
regression/ # cross-cutting cases spanning the dispatch surface
runtests.jlruntests.jl does three things before any include, and all three matter:
it
importsDECUHR,IntegralsandNonlinearSolveso the package extensions activate — several tests cross-validatemethod = :decuhragainst the residue and nested-QuadGK backends, and exercise the SciML self-consistent solvers. Without those imports the corresponding methods do not exist and the tests silently cover less than they appear to;it calls
Random.seed!once, so a CI failure is reproducible locally instead of depending on the draw;it wraps everything in a single top-level
@testset "MeanFieldHomogenization".
What a good test looks like here
Every new public function ships with at least a smoke test — but a smoke test is a floor, not a target. The tests that actually catch bugs in this package are of three kinds:
Closed-form oracles. Where an analytical result exists, assert against it at tight tolerance;
rtol = 1e-13is routinely achievable. Example: the penny-crack conduction COD intest/Cracks/test_thermal.jl.Cross-backend agreement. The same quantity computed two independent ways must agree —
:residuesvs:decuhr; quadrature vs the BigFloat monomial series (test/LayeredSpheroids/test_coupling.jl); the general series solution vs the perfect-interface closed form (test/LayeredSpheroids/test_conductivity.jl).Invariants. Identities that hold whatever the numbers:
, the Eshelby identities, or the contribution invariant for a layered particle.
Test an asymmetric case, not only the symmetric one
Degenerate cases hide convention errors, because competing conventions tend to agree there. A penny crack has MeanFieldHomogenization, by Echoes and by the literature all coincide (see Crack opening displacement, section Conventions). A suite covering only the penny therefore cannot detect a wrong
This is not hypothetical. As of 2026-07 the elastic
Number types are part of the contract
The package is generic over the scalar type, and that genericity is advertised in the dispatch tables of the theory pages — so it is tested explicitly:
| type | role |
|---|---|
Float64 | the default path |
ForwardDiff.Dual | every algorithm advertised as AD-safe must be exercised through derivative, not merely called with a Dual |
BigFloat | precision oracle, notably for the spheroid coupling matrices |
symbolic (SymPy, Symbolics) | closed-form paths only |
When adding an algorithm, state its AD compatibility in the docstring and back that claim with a test.
Coverage
Run coverage through .github/scripts/coverage.jl rather than the stock julia-processcoverage action.
The stock processor misattributes lines inside generated functions and multi-line expressions — observed reporting ~59 % where the true figure is ~95 %. Trust the script's numbers over the action's.
Checking the documentation without a full build
docs/make.jl re-executes every page and takes tens of minutes, most of it spent on pages nobody just edited. For the edit/check loop use the partial check instead — it runs only the @setup / @example / @repl blocks of the pages you name, one fresh module per block group, exactly as Documenter sandboxes them:
julia --project=docs docs/check_blocks.jl theory/hill_tensors.md manual/
julia --project=docs docs/check_blocks.jl $(git diff --name-only -- 'docs/src/*.md')It catches what breaks a build in practice: undefined names, a binding that shadows an import (strip = ... over Base.strip), state a block silently inherited from elsewhere, method errors. Exit status is non-zero on any failure.
It does not replace the full build, which is still what validates cross-references, the pages tree, citations and the page-size thresholds.
And neither of them can tell you that an interactive figure came out blank: Documenter reports nothing, and the HTML is well-formed either way. That needs a browser, with software WebGL enabled — the exact invocation is recorded at the end of docs/check_blocks.jl. Without those flags plotly.js reports "WebGL is not supported by your browser" and every 3-D scene renders as a gray box, which looks like a broken figure and is not one.