ESP32 Test Plan
Every implementation contract needs a test plan; maximise tests that run without hardware, and make hardware tests explicit.
Tools
If the esp32-workbench MCP server is connected, call its tools; otherwise run the
CLI. If esp32wb is not on PATH, use
uvx --from git+https://github.com/agodianel/esp32-claude-workbench esp32wb <args>.
Add --json for machine-readable output (useful as CI evidence).
| Static layer (no hardware) | MCP tool | CLI |
|---|---|---|
| All static checks | project_doctor | esp32wb doctor . (non-zero exit on errors) |
| Pin audit for the target | pin_audit | esp32wb pins . |
| Stack sizes | stack_check | esp32wb stacks . |
| sdkconfig review | sdkconfig_review | esp32wb sdkconfig sdkconfig |
| Partition fit vs built app | partition_check | esp32wb partitions partitions.csv --bin build/<app>.bin |
| Mission completeness | validate_mission | esp32wb mission validate missions/<file>.md |
Use the test plan template.
Steps
1. Classify the change → layers
| Layer | Applies when | Tooling |
|---|---|---|
| 0. Static | Always | esp32wb doctor, compiler warnings (-Werror where possible), idf.py clang-check / cppcheck if available |
| 1. Repository logic | Python tooling, scripts, templates | pytest |
| 2. Host-side C logic | Pure logic: parsers, state machines, protocol encoders, math | Native build (CMake + Unity/CMock, or ESP-IDF linux target where the component supports it) |
| 3. On-target unit | Component behaviour needing the real chip or drivers | ESP-IDF Unity test app per component (test_apps/), run via pytest-embedded |
| 4. Target integration | End-to-end behaviour: boot, Wi-Fi, peripherals, OTA, power | pytest-embedded with pytest-embedded-idf + pytest-embedded-serial-esp |
Record the target chip for layers 3–4; a test passing on classic ESP32 says nothing about ESP32-S3 or a RISC-V chip.
2. Enumerate cases per layer
- Positive: valid inputs, expected behaviour.
- Negative: invalid input, NULL handles, driver returning errors, missing NVS keys.
- Boundary: buffer sizes, max lengths, timeouts, ADC/PWM range ends, queue full.
- Concurrency: two tasks on shared state, ISR + task, dual-core contention (if the chip is dual-core).
- Error / recovery: bus NACK/timeout, Wi-Fi drop and reconnect, brownout, OTA failure + rollback.
- Resource: heap and stack high-water marks before/after; no leak over N iterations.
3. Pass/fail criteria and evidence
For each case: expected result, tolerance (timing/analog), and evidence — test output, assertion count, serial log excerpt, measured value, or tool JSON.
4. Infrastructure
New test files, fixtures/mocks, test data, sdkconfig for tests
(sdkconfig.ci.*), and hardware setup (board, wiring, instruments).
5. Write the plan
Fill the template; store it with the mission (or in the mission's Test Plan section for small changes).
pytest conventions
- Mark hardware tests so CI can skip them:
python
@pytest.mark.hardware @pytest.mark.esp32s3 # pytest-embedded target marker, if used def test_wifi_reconnect(dut) -> None: dut.expect("Got IP", timeout=30) - Keep host tests free of serial ports and network.
- Type hints,
pathlib.Path, fixtures for shared setup (project standard).
Rules
- No contract without a test plan; tiny changes may use a 2–3 line plan.
- Prefer extracting pure logic so it can be tested on the host.
- Every hardware test is explicitly marked and lists required wiring.
- Include negative tests — happy-path-only is insufficient for firmware.
- "What was not tested and why" is a mandatory section.


