Sprint 3 — Testing infrastructure¶
Objective. Provide reusable test utilities to accelerate library and application testing.
Deliverable. “Application tests can verify lifecycle behavior without boilerplate.”
Status¶
Implemented in lifecore_ros2.testing as an extensible package with focused
submodules:
fakesfor reusable lifecycle, topic, timer, service, and client fakesfixturesfor pytest-compatible node fixturesassertionsfor lifecycle state, transition order, and activation gating checkshelpersfor common activate/deactivate and logging flowsconcurrencyfor small threaded transition probes
The package uses concrete ROS 2 message/service types for the standard topic and
service fakes: std_msgs.msg.String and std_srvs.srv.Trigger.
Documentation entry points:
User guide: Testing Utilities
API reference: API Reference
—
Content¶
Fake component implementations¶
FakeComponent— minimal component that tracks state (called hooks, hook order, failure modes)FakeServiceComponent— returns fixed response or raises on demandFakeClientComponent— simulates service call success/failureFakePublisherComponent— publishes fixed messagesFakeSubscriberComponent— tracks received messagesFakeTimerComponent— timer with controlled tick scheduling
Test fixtures¶
lifecycle_node_fixture— creates aLifecycleComponentNodefor test, tears down afternode_with_components— fixture that pre-registers standard fake componentsassert_component_state(node, name, expected_state)— verify component stateassert_transition_order(log, expected_order)— verify hook call orderassert_activation_gated(component)— verify gating works
Helpers for common patterns¶
activate_component(node, name)— configure + activate in one call (test shorthand)deactivate_component(node, name)— deactivate + cleanup (test shorthand)collect_logs(logger, test_fn)— capture logs during testexpect_log(logs, pattern)— assert a log line matches patternFailingComponent(fail_at_hook='_on_configure', exception=RuntimeError())— component that fails on demand
Concurrency helpers¶
spawn_transition_thread(node, transition_name)— call transition from thread (for race testing)assert_no_race(test_fn)— run test multiple times to catch racy behaviorbarrier_hook— hook that blocks until signaled (for orchestrating concurrent tests)
—
Tests to write¶
Fixture tests¶
[x]
lifecycle_node_fixturecreates and destroys node cleanly[x]
FakeComponenttracks hook calls and state transitions[x]
FakeComponentcan be configured to fail at specific hook[x]
assert_component_statepasses/fails correctly
Helper tests¶
[x]
activate_componentcalls configure then activate in order[x]
collect_logscaptures all logs during test[x]
expect_logfinds or asserts missing pattern[x]
spawn_transition_threadruns transition from thread safely
Integration example tests¶
[x] Using fixtures to test a node with standard fake components
[x] Verifying hook call order with
assert_transition_order[x] Verifying activation gating with
assert_activation_gated
—
Risks and mitigation¶
Risk 1: Fixtures not flexible enough
Problem: Test needs custom component behavior not covered by fakes.
Mitigation: - Fakes are subclassable; easy to override hooks - Fixture accepts kwargs for customization - Document: “compose fakes + custom components as needed”
Risk 2: Tests become harder to debug (hidden in helpers)
Problem: Helpers abstract away details; tests are less readable.
Mitigation: - Helpers are minimal and obvious (e.g.,
activate_componentjust calls two methods) - Test docstrings explain what is being verified - Logging is always available for debugging
Risk 3: Infrastructure becomes overspecialized
Problem: Infrastructure is so tailored to lifecycle that it’s not useful elsewhere.
Mitigation: - Fakes and fixtures use only public API - No hidden mocking or patching - Document as reusable utilities, not black boxes
—
Dependencies¶
Requires:
LifecycleComponent+LifecycleComponentNode(shipped)Requires: pytest (dev dependency, shipped)
Recommends: Error handling (Sprint 2) so fakes can simulate failures
Recommends: Service/Client (Sprint 1) so fakes can simulate those
—
Scope boundaries¶
In-scope:
Reusable test components (FakeComponent, FakeServiceComponent, etc.)
Fixtures for common setups
Helper functions for assertions
Logging and concurrency utilities for tests
Out-of-scope:
Full mock/patch system (use
unittest.mockif needed)Property-based testing (use Hypothesis if needed)
Performance benchmarking utilities
CI-specific tooling
—
Success signal¶
[x]
from lifecore_ros2.testing import FakeComponent, lifecycle_node_fixture, activate_componentworks[x] Public utilities have concise docstrings
[x] Example usage is covered by
tests/testing[x] Generic lifecycle test doubles were migrated to use these utilities
[x] Ruff, Pyright, Pytest passed on the targeted test migration
[x] No external testing library added (pytest remains a development dependency)
—
Location¶
Module package:
src/lifecore_ros2/testing/Tests:
tests/testing/plus migrated existing lifecycle/component testsExamples:
docs/testing.rstand focused tests undertests/testing/