Chapter 12Lesson 02230–320 min

Resource Files, Variable Files, Library Imports, and Project Layout: Guided Hands-On Workflow

Refactor a disposable monolithic suite into explicit tests, resources, variables, libraries, data, and results boundaries; then verify path behavior with dry-run, Libdoc, and two launch locations.

Hands-onProject layoutCURDIR/EXECDIR--pythonpathLibdoc

Learning objectives

  • Split a monolithic suite into tests, resources, variable files, a tiny Python library, data, and results.
  • Use ${CURDIR} for source-relative fixture paths and an explicit project-root --pythonpath for library imports.
  • Inspect ${CURDIR}/${EXECDIR} without depending on either accidentally.
  • Verify resource/library imports with --dryrun and Libdoc.
  • Run the same project from two working directories using one documented command contract.

Current compatibility baseline. Verified 2026-08-31: Robot Framework 7.4.2 is the current stable release and requires Python 3.8+; 7.5b1 is a pre-release and is not required in this chapter. In 7.4.2, .resource is the recommended resource-file extension. Resource and Settings-level variable-file paths are resolved relative to the importing data file first and then via Python's module search path. ${CURDIR} is the absolute directory of the current data file; ${EXECDIR} is the absolute directory where execution started. JSON variable files require no optional package; YAML variable files require PyYAML.

1. Disposable scenario: normalize and validate synthetic build labels

The lab operates only on local synthetic strings and a small text fixture. There is no browser, API, database, SSH service, container, or production target. The custom Python library performs a pure string normalization; the resource file expresses the domain-level validation; variable files supply non-sensitive configuration.

2. Preflight and version evidence

python --version
python -m robot --version
python -c "import os, sys; print(os.getcwd()); print(sys.executable)"

Use an isolated environment from Chapter 02 and pin Robot Framework 7.4.2 for this lab. Record the actual Python version and working directory. Do not install PyYAML: the mandatory non-Python variable-file example uses JSON.

3. Start from a deliberately monolithic suite

*** Variables ***
${PREFIX}    build-
${EXPECTED}    BUILD-ALPHA

*** Test Cases ***
Normalize label
    ${raw}=    Set Variable    ${PREFIX}alpha
    ${normalized}=    Convert To Upper Case    ${raw}
    Should Be Equal    ${normalized}    ${EXPECTED}

This is small enough to work, but it hides future boundaries. If ten suites need the same normalization contract or environment-independent values, copying these definitions multiplies drift.

4. Target project layout

rf12-layout-lab/
├── tests/
│   └── labels.robot
├── resources/
│   └── labels.resource
├── variables/
│   ├── demo.py
│   └── demo.json
├── libraries/
│   ├── __init__.py
│   └── text_tools.py
├── data/
│   └── sample-label.txt
└── results/

The folders communicate ownership. tests/ expresses scenarios. resources/ contains Robot-level abstractions. variables/ contains imported configuration values. libraries/ is Python capability code. data/ is ordinary fixture content. results/ is generated evidence and should normally be ignored by source control.

5. Add one tiny Python library — and keep it pure

# libraries/text_tools.py

def normalize_label(value: str) -> str:
    """Normalize a synthetic build label without external side effects."""
    return value.strip().upper()

This module owns a low-level transformation only. It does not read environment variables, files, credentials, or network endpoints during import. Chapter 20 will teach full custom-library architecture; here the purpose is to understand import placement.

6. Add Python and JSON variable files with different responsibilities

# variables/demo.py
PREFIX = "build-"
EXPECTED = "BUILD-ALPHA"
{
  "fixture_name": "sample-label.txt",
  "expected_file_value": "build-beta"
}

The Python file demonstrates normal Python variable-file import. The JSON file demonstrates a supported data-only format without optional dependencies. If you choose YAML later, pin/install PyYAML explicitly and record that dependency.

7. Move the domain contract into a .resource file

*** Settings ***
Library      libraries.text_tools
Variables    ../variables/demo.py
Variables    ../variables/demo.json
Library      OperatingSystem
Library      String

*** Keywords ***
Label Should Normalize
    [Arguments]    ${raw}    ${expected}
    ${actual}=    Normalize Label    ${raw}
    Should Be Equal    ${actual}    ${expected}

Fixture Label Should Match
    ${fixture_path}=    Set Variable    ${CURDIR}/../data/${fixture_name}
    ${raw}=    Get File    ${fixture_path}
    ${raw}=    Strip String    ${raw}
    Should Be Equal    ${raw}    ${expected_file_value}

${CURDIR} here means the directory containing labels.resource, so the fixture path remains tied to that source file no matter where the process is launched. The Python library import by module name requires the project root on the module search path; that is a runner responsibility.

8. Keep the suite focused on intent

*** Settings ***
Resource    ../resources/labels.resource

*** Test Cases ***
Normalize configured label
    Label Should Normalize    ${PREFIX}alpha    ${EXPECTED}

Read source-relative fixture
    Fixture Label Should Match

Observe path identities
    Log    CURDIR=${CURDIR}
    Log    EXECDIR=${EXECDIR}

The suite imports one domain resource. It does not need to know where the Python library or JSON file lives because that is owned by the resource boundary. The final test logs path identities as evidence; it should not make correctness depend on a particular ${EXECDIR}.

9. Create the synthetic data fixture

build-beta

Save this exact text as data/sample-label.txt. The fixture is ordinary data, not a Robot variable file. The resource reads it explicitly with OperatingSystem.Get File.

10. Define one launch contract that works from different cwd values

Set RF12_ROOT once to the absolute path of rf12-layout-lab. The command contract is then “all project paths are derived from that root; cwd is irrelevant.”

Bash / zsh:

export RF12_ROOT="/absolute/path/to/rf12-layout-lab"
python -m robot   --pythonpath "$RF12_ROOT"   --outputdir "$RF12_ROOT/results/from-root"   "$RF12_ROOT/tests"

PowerShell:

$env:RF12_ROOT = "C:\absolute\path\to\rf12-layout-lab"
python -m robot `
  --pythonpath $env:RF12_ROOT `
  --outputdir "$env:RF12_ROOT\results\from-root" `
  "$env:RF12_ROOT\tests"

Now change to any unrelated directory and run the same command contract with a different output subdirectory such as results/from-other-cwd. ${EXECDIR} will differ; imports and fixture lookup should not.

11. Verify imports before executing behavior

python -m robot   --pythonpath "$RF12_ROOT"   --dryrun   --outputdir "$RF12_ROOT/results/dryrun"   "$RF12_ROOT/tests"

python -m robot.libdoc   --pythonpath "$RF12_ROOT"   libraries.text_tools   "$RF12_ROOT/results/text_tools.html"

python -m robot.libdoc   --pythonpath "$RF12_ROOT"   "$RF12_ROOT/resources/labels.resource"   "$RF12_ROOT/results/labels-resource.html"

Dry-run validates parsing, imports, keyword resolution, and argument contracts without running normal library keywords. Libdoc proves that Robot can resolve the Python module/resource and exposes the available keyword signatures/documentation. These checks complement—not replace—a real test run.

12. Expected evidence

Evidence Expected observation
output.xml/report.html Three tests PASS for the clean lab
log.html CURDIR points to tests/ in the suite and to resources/ when evaluated in the resource; EXECDIR matches launch cwd
Libdoc library HTML Normalize Label is discoverable from libraries.text_tools
Libdoc resource HTML Label Should Normalize and Fixture Label Should Match are documented
Result tree Generated files stay under results/, not tests/ or resources/

13. Challenge: choose the correct path layer

You add data/more/sample.txt used only by resources/labels.resource. Should you (a) rely on cwd, (b) add data/ to --pythonpath, or (c) anchor the fixture to the resource with ${CURDIR}? Choose and justify the owner. Then explain why a Python library module belongs on the module search path instead.

14. Knowledge check

Why does the Python library import use --pythonpath but the fixture file does not?

What changes when the same command contract is launched from another directory?

Does a successful Libdoc run prove the business test passes?

Why use JSON instead of YAML in the mandatory lab?

15. Summary and next step

You now have a small project whose source boundaries and runner contract are explicit. Resources own Robot-level abstractions, variable files own values, Python libraries own capability code, data stays ordinary data, and results stay generated evidence. Lesson 3 turns these mechanics into design decisions about search paths, resource granularity, variable-file formats, packaging, and dependency direction.

Next lesson

Resource Files, Variable Files, Library Imports, and Project Layout: Configuration, Design Patterns, and Trade-Offs

Continue with Resource Files, Variable Files, Library Imports, and Project Layout: Configuration, Design Patterns, and Trade-Offs. 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.

Further reading

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.