Chapter 20Lesson 05240–300 min

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.

Checkpoint labTyped APILibdoc evidenceScope repairConversion diagnostics

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:

  1. Change the library from TEST to GLOBAL scope and prove the second test observes leaked instance state.
  2. 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 raises ValueError; adapter maps it to Failure with 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 int return metadata.
  • Accept Demo Secret documents Secret.
  • helper_not_exposed is 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.
  • @library disables 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?

What proves an invalid float argument failed before the keyword body?

Why do we preserve Libdoc output in the checkpoint evidence packet?

What would be a false repair for the GLOBAL leak?

Why is Chapter 21 not a reason to implement this library dynamically now?

Next lesson

Dynamic and Hybrid Libraries, Remote Libraries, and Extension Architecture: Core Concepts and Mental Model

Continue with Dynamic and Hybrid Libraries, Remote Libraries, and Extension Architecture: Core Concepts and Mental Model. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

References and version anchors

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.