Tutorial 5 — Hidden code blocks¶
The best documentation examples focus on one concept at a time. But tests need setup, teardown, and assertions that would clutter a documentation example. Hidden blocks solve this: you put the boilerplate inside an HTML comment so readers never see it, but the plugin runs it.
Prerequisites¶
Completed Tutorial 4 — Subtests. You understand split blocks and fixtures.
The idea¶
An HTML comment in Markdown hides its content from all renderers. If you put a Python code block inside the comment, readers do not see it — but markdown-pytest parses inside comments and runs the code.
Variant 1 — Hidden setup before a visible block¶
The most common pattern: hidden imports and setup, visible demo code.
In the raw Markdown source you write:
<!--
name: test_csv_demo;
fixtures: tmp_path
```python
import csv, io
csv_file = tmp_path / "data.csv"
csv_file.write_text("name,score\nAlice,95\nBob,87\n")
```
-->
```python
rows = []
with open(csv_file) as f:
for row in csv.DictReader(f):
rows.append(row)
```
What readers see when the Markdown is rendered:
rows = []
with open(csv_file) as f:
for row in csv.DictReader(f):
rows.append(row)
Clean, focused, no boilerplate. But the test runs the hidden block first,
so csv_file exists when the visible block runs.
Variant 2 — Hidden assertions after a visible block¶
Use split blocks with a hidden second block to add assertions that readers do not need to see:
<!-- name: test_csv_demo -->
```python
rows = []
with open(csv_file) as f:
for row in csv.DictReader(f):
rows.append(row)
```
<!--
name: test_csv_demo
```python
assert len(rows) == 2
assert rows[0] == {"name": "Alice", "score": "95"}
```
-->
Readers see only the for loop. The assertions are hidden but still run.
Step 1 — The complete polished pattern¶
Combine both variants: hidden setup before, visible demo, hidden assertions after. This is the recommended pattern for tutorial-style documentation.
In the raw Markdown:
<!--
name: test_json_roundtrip;
fixtures: tmp_path
```python
import json
path = tmp_path / "config.json"
```
-->
Write a config file:
```python
config = {"debug": True, "workers": 4}
path.write_text(json.dumps(config))
```
Read it back:
```python
loaded = json.loads(path.read_text())
assert loaded == config
```
<!--
name: test_json_roundtrip
```python
assert loaded["debug"] is True
assert loaded["workers"] == 4
```
-->
Readers see two code blocks — write and read — with prose between them. They look like a natural tutorial. The hidden blocks take care of imports, file setup, and final assertions.
The blocks with the same name (test_json_roundtrip) are combined in
document order:
Hidden setup block (imports,
path)Visible “write” block
Visible “read” block
Hidden assertion block
Step 2 — Try it yourself¶
Create guide.md with this content (copy the raw text including the HTML
comments — not the rendered version above):
## JSON round-trip
<!--
name: test_json_roundtrip;
fixtures: tmp_path
```python
import json
path = tmp_path / "config.json"
```
-->
```python
config = {"debug": True, "workers": 4}
path.write_text(json.dumps(config))
loaded = json.loads(path.read_text())
```
<!--
name: test_json_roundtrip
```python
assert loaded == config
```
-->
Run:
pytest guide.md -v
guide.md::test_json_roundtrip PASSED
View the rendered result in any Markdown viewer — only the config =...
block is visible.
Step 3 — Hidden block syntax rules¶
The hidden block is a code block placed inside an HTML comment that
also contains the name: metadata:
<!--
name: test_foo
```python
# this code runs but is hidden from readers
x = 1
–>
Note that the closing `-->` is on its own line **after** the closing
` ``` `. The opening ` ```python ` and closing ` ``` ` are inside the
comment.
The comment can also carry other metadata. Put the metadata keys first,
then the code fence:
```{code-block} text
<!--
name: test_foo;
fixtures: tmp_path
```python
p = tmp_path / "f.txt"
–>
## Step 4 — Multiple hidden blocks
You can have more than one hidden block per test. They run in document
order along with the visible blocks:
````{code-block} text
<!--
name: test_multi_hidden;
fixtures: tmp_path
```python
import json
path = tmp_path / "x.json"
–>
path.write_text(json.dumps({"a": 1}))
## Debugging hidden blocks
Because hidden blocks are inside HTML comments, they do not show in
rendered Markdown. If a test fails and you are not sure which hidden block
is the problem, view the raw Markdown source and look for comments that
contain code fences.
The traceback always includes a line number pointing to the original
Markdown file — you can jump straight to that line to find the relevant
block.
## What you learned
- Place a code block inside an HTML comment to hide it from readers.
- Hidden blocks run in document order with visible blocks.
- The recommended pattern: hidden setup → visible demo → hidden assertions.
- The hidden block's comment must contain the `name:` key and a code fence.
## Next steps
{doc}`06_async` shows how to test async code — functions that use
`await` — with just an `async ` prefix on the test name.