Checkpoint Lab — Writing Python Test Libraries and the Library API
Build and repair a typed synthetic Python library, prove its Libdoc and unit-test contracts, trigger a scope leak and an argument-conversion failure, and reconcile Python evidence with Robot results.
Learning objectives
- Build a typed Python library around a synthetic domain and prove the domain independently with unit tests.
- Generate Libdoc evidence and verify only the intended keyword surface is exposed.
- Predict and diagnose a GLOBAL-scope leak using Robot result evidence.
- Predict and diagnose an argument-conversion failure before the Python keyword body executes.
- Repair the API contract, rerun controlled slices, and produce a concise evidence packet bridging to Chapter 21.
Current compatibility baseline — verified 2026-08-31.
Robot Framework 7.4.2 is the stable course baseline
and requires Python 3.8+. The lab uses the documented static Python
library API, robot.api.deco.library,
robot.api.deco.keyword, robot.api.logger,
public exceptions from robot.api,
robot.api.types.Secret, and Libdoc. The
@library decorator disables automatic keyword discovery
by default; keyword methods are therefore exposed deliberately with
@keyword. Function argument annotations participate in
automatic argument conversion. Return annotations and
@keyword(types={"return": ...}) are documentation
metadata in current Robot Framework and do not convert
returned objects during execution. Secret arguments are masked in
Robot representations but are not encrypted and can still be
disclosed by code that accesses .value. The mandatory
path uses only Python, Robot Framework core, and Python's standard
unittest; no browser, API, database, SSH, Pabot, CI
provider, container runtime, paid platform, or production target is
required.
1. Checkpoint scenario and success criteria
Build the same local rf20-library-lab from Lesson 2.
The checkpoint has two independent failure injections:
- Change the library from TEST to GLOBAL scope and prove the second test observes leaked instance state.
-
Pass a non-numeric value to an
float-annotated argument and prove Robot rejects it at the boundary before the method body.
Then restore TEST scope, restore valid input, regenerate Libdoc, and produce evidence showing Python unit tests, keyword API, scope behavior, and Robot status agree.
Isolation. The domain is synthetic. The lab writes only source/evidence files under its own project directory and reads one fake environment variable. It does not contact a browser, API, database, SSH host, email system, package registry during execution, or production service.
2. Exact project tree and source files
rf20-library-lab/
├── src/rf20_domain/
│ ├── __init__.py
│ ├── domain.py
│ └── library.py
├── tests/unit/test_domain.py
├── tests/robot/inventory.robot
└── evidence/
├── baseline/
├── broken-global/
├── broken-conversion/
└── repaired/
Use the domain.py, library.py, unit-test,
and Robot-suite sources from Lesson 2. Record a checksum or Git diff
before failure injection if you want a stronger rollback trail.
3. Preflight and version evidence
python --version
python -m robot --version
python -m robot.libdoc --help
python -c "import robot; print(robot.__version__); print(robot.__file__)"
# Bash/zsh
export PYTHONPATH="$PWD/src"
export RF20_DEMO_TOKEN='token-FAKE_DO_NOT_USE'
# PowerShell equivalents:
# $env:PYTHONPATH = (Resolve-Path ".\src").Path
# $env:RF20_DEMO_TOKEN = 'token-FAKE_DO_NOT_USE'
Expected baseline: Robot Framework 7.4.2, Python supported by that release, and imports resolving from the activated environment/project source. Record exact outputs rather than assuming the shell is correct.
4. Predict before execution
| Prediction | Expected state change | Independent verification |
|---|---|---|
| Baseline TEST scope | First test count becomes 1; second starts at 0 | Robot assertions + log hierarchy |
| GLOBAL injection | First test leaves shared count 1; second starts at 1 and fails | Broken output.xml/log + Libdoc scope/version docs/source diff |
| Conversion injection | Keyword body does not run for invalid price | Conversion failure mentions price/float; no quote INFO log for call |
| Repaired contract | All tests pass with TEST scope and valid price | New result directory + source review + Libdoc regeneration |
| Secret contract | Fake token literal is absent from durable logs | Search output.xml/log.html for the fake value |
5. Baseline: unit tests, Libdoc, and Robot integration
mkdir -p evidence/baseline/libdoc evidence/baseline/robot
python -m unittest discover -s tests/unit -v \
> evidence/baseline/unittest.txt 2>&1
python -m robot.libdoc -P src \
rf20_domain.library.InventoryLibrary \
evidence/baseline/libdoc/InventoryLibrary.html
python -m robot.libdoc -P src \
rf20_domain.library.InventoryLibrary list \
> evidence/baseline/libdoc/keywords.txt
python -m robot --pythonpath src \
--outputdir evidence/baseline/robot \
tests/robot/inventory.robot
PowerShell can run the same commands on one line or with backtick continuation. Baseline acceptance: unit tests pass; Libdoc exposes only three intended keywords; Robot tests pass; second test starts with count 0; fake token is not present in result artifacts.
6. Failure injection A: make object lifetime wrong
# In library.py, change only this line:
@library(scope="GLOBAL", version="1.0.0")
class InventoryLibrary:
...
Before running, predict which test fails and why. Then run only the Robot suite into a new directory:
python -m robot --pythonpath src \
--outputdir evidence/broken-global \
tests/robot/inventory.robot
Preserve the non-zero exit status and result files. The failure should occur because the second test observes state left by the first test in the same Python object. Do not add a retry, clear the list inside the assertion, or weaken the expected value.
7. Diagnose GLOBAL scope from two evidence planes
Python/source plane: the decorator now states GLOBAL. Robot/runtime plane: the first test appends one SKU; the second test receives the same library instance and sees count 1. This explains why running the second test alone can produce a different result from running the full suite.
Repair by restoring scope="TEST". Regenerate Libdoc if
your documentation exposes scope/version notes, then rerun the suite
into evidence/repaired/scope. The repaired result must
be a new artifact, not an overwrite of the broken-global run.
8. Failure injection B: violate a typed argument contract
# Temporarily replace the valid price in the first test:
${quote}= Quote Line ab-12 not-a-number 2
python -m robot --pythonpath src \
--outputdir evidence/broken-conversion \
tests/robot/inventory.robot
The expected failure is at Robot argument conversion for
unit_price: float. The library method should not log
“Quoted sku=...” for this invocation because the method body is not
entered. This is different from passing -1, which
converts to float successfully and then fails the domain rule inside
Python.
9. Compare boundary failure with domain failure
Create one additional optional controlled run using
-1 as the price. Compare:
-
not-a-number: conversion boundary rejects the input before method execution. -
-1: conversion succeeds; domain logic raisesValueError; adapter maps it toFailurewith SKU context.
This comparison teaches where the error originates. Do not catch conversion errors inside the method—they happen before the method is called.
10. Repair and re-establish the contract
# Restored contract
@library(scope="TEST", version="1.0.0")
class InventoryLibrary:
...
# Restored valid call
${quote}= Quote Line ab-12 12.50 2
mkdir -p evidence/repaired/libdoc evidence/repaired/robot
python -m unittest discover -s tests/unit -v \
> evidence/repaired/unittest.txt 2>&1
python -m robot.libdoc -P src \
rf20_domain.library.InventoryLibrary \
evidence/repaired/libdoc/InventoryLibrary.html
python -m robot --pythonpath src \
--outputdir evidence/repaired/robot \
tests/robot/inventory.robot
The repaired run is accepted only if unit tests, Libdoc keyword exposure, Robot status, scope probe, and redaction check all agree. One green Robot process without the other evidence is not the full checkpoint.
11. Required Libdoc inspection
Open the repaired Libdoc and verify:
- Library name/version resolve from the intended module/class.
-
Quote Line documents
raw_sku,unit_price: float,quantity: int, and a mapping return type. -
Quoted Count shows
intreturn metadata. -
Accept Demo Secret documents
Secret. helper_not_exposedis absent.
Remember that return annotations are documentation metadata, not runtime return conversion. The unit test/Robot assertions prove actual returned values.
12. Evidence packet
| Artifact | Required observation | Purpose |
|---|---|---|
baseline/unittest.txt |
All domain tests pass | Framework-independent logic baseline |
| Baseline Libdoc HTML/list | Only intended keyword surface | Discovery/API contract |
| Baseline Robot output/log/report | All tests pass; TEST scope probe succeeds | Integration baseline |
| Broken-global Robot output/log/report | Second test fails with leaked count | Object-lifetime failure evidence |
| Broken-conversion output/log/report | Typed argument rejected before method body | Boundary conversion evidence |
| Repaired unit/Libdoc/Robot outputs | All contracts agree after minimal repair | Correction proof |
| Redaction search result | Fake token literal absent | Secret/logging contract |
| Exact version/command notes | Interpreter + Robot + search path | Reproducibility |
13. Verification checklist
- Exactly one Python interpreter/Robot environment is used for unit/Libdoc/Robot commands.
- Pure domain code does not import Robot Framework.
-
@librarydisables accidental auto exposure and all intended keywords use@keyword. - Library scope is TEST after repair; no mutable module/global state is required.
- Typed numeric conversion works for valid values and fails before method execution for invalid strings.
- Return type metadata is not described as automatic return conversion.
- Secret object is never unwrapped into a log/exception/result field.
- Libdoc and Robot import use the same explicit source/search-path contract.
- First-failure evidence remains preserved in separate directories.
-
No private
robot.*internals are required by the library.
14. Cleanup / rollback
Restore the source to TEST scope and the valid input call before
finishing. Keep the evidence directories if they are part of course
work; otherwise remove only the disposable
rf20-library-lab directory and its virtual environment.
There is no remote state to roll back.
If using Git, git diff should show only the intended
lab files. Never remove or overwrite unrelated Python environments,
package caches, or system libraries as cleanup.
15. What Chapter 20 adds—and the bridge to Chapter 21
Chapter 20 adds a governed Python extension boundary to the production Robot operating model: independently testable domain code, an explicit static keyword API, type-aware arguments, documented return contracts, deliberate instance scope, public status/logging APIs, secret-aware handling, reproducible imports, and Libdoc as API evidence.
Chapter 21 builds on this stable foundation to examine dynamic and hybrid libraries, Remote libraries, and extension architecture. Those mechanisms add runtime keyword discovery and network/serialization boundaries, so the static API from this chapter remains the default baseline against which added complexity must be justified.
Knowledge check
Why does the GLOBAL failure demonstrate a Python library problem rather than a Robot suite-variable problem?
The leaked value lives in self._quoted_skus on one GLOBAL Python object. Robot variables did not create or share that object.
What proves an invalid float argument failed before the keyword body?
Robot reports argument conversion failure and the method-specific INFO log/side effect is absent for that invocation.
Why do we preserve Libdoc output in the checkpoint evidence packet?
It independently records library discovery, exposed keyword names, signatures/types, and version metadata—the API surface between Python and Robot.
What would be a false repair for the GLOBAL leak?
Adding retries, changing the second test expectation to 1, or clearing hidden shared state inside the assertion. The correct repair is to restore the intended ownership/scope contract.
Why is Chapter 21 not a reason to implement this library dynamically now?
Static discovery already satisfies the known keyword set. Dynamic/hybrid/Remote APIs add complexity and should be introduced only when runtime metadata/discovery/network boundaries are truly required.
References and version anchors
- Robot Framework 7.4.2 — Creating test libraries — checkpoint behavior baseline
- Robot Framework 7.4.2 — Libdoc — API documentation evidence
- Robot Framework public API — stable integration boundary
- Robot Framework Secret type — 7.4 secret typing and warnings
- Robot Framework PyPI — current stable/pre-release anchor
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.