Chapter 05Lesson 02150–200 min

Keywords, Arguments, Return Values, and Reusable Abstractions: Guided Hands-On Workflow

Refactor duplicated Robot steps into reusable user keywords, evolve signatures from simple required/default arguments to named-only and typed contracts, return values explicitly, and inspect the resulting call hierarchy.

Hands-on refactorRequired/default argsNamed-onlyLibdocCall hierarchy

Learning objectives

  • Create a disposable BuiltIn/standard-library-only suite containing intentional duplication, then prove its baseline behavior.
  • Extract user keywords without changing the observable test outcomes and identify what each caller must still own.
  • Add required, default, named, named-only, typed, varargs, and free-named arguments only where they solve a real interface problem.
  • Return one and multiple values with native RETURN and verify caller ownership.
  • Use Libdoc and log.html as interface/execution evidence, then complete a small design challenge.

Disposable lab boundary. Use Robot Framework 7.4.2 in the isolated Chapter 02 environment. Work only in a temporary rf-keyword-lab directory with synthetic release identifiers. The lab imports only the standard String library and never touches a browser, API, database, SSH target, CI secret, or production system.

1. Preflight: prove the interpreter and empty lab state

Start by recording the runtime identity and a clean target directory. This makes the later signature/result evidence reproducible.

python --version
python -m robot --version

mkdir rf-keyword-lab
cd rf-keyword-lab

PowerShell users may create the directory with New-Item -ItemType Directory rf-keyword-lab. Do not reuse a directory containing old output.xml or log.html files; stale evidence can mislead the comparison.

2. Start with a flat, duplicated suite

Create release_flat.robot. The duplication is intentional: both tests normalize a synthetic release identifier and build a display label with the same sequence.

*** Settings ***
Library    String

*** Test Cases ***
API Release Label Is Stable
    ${raw}=    Set Variable    API Candidate 42
    ${lower}=    Convert To Lower Case    ${raw}
    ${slug}=    Replace String    ${lower}    ${SPACE}    -
    ${label}=    Catenate    SEPARATOR=:    api    ${slug}
    Should Be Equal    ${label}    api:api-candidate-42

UI Release Label Is Stable
    ${raw}=    Set Variable    UI Candidate 42
    ${lower}=    Convert To Lower Case    ${raw}
    ${slug}=    Replace String    ${lower}    ${SPACE}    -
    ${label}=    Catenate    SEPARATOR=:    ui    ${slug}
    Should Be Equal    ${label}    ui:ui-candidate-42

Run it once and preserve the result directory as the behavior baseline.

python -m robot --outputdir evidence/flat release_flat.robot

Expected result: two tests, both PASS. The important evidence is not merely green status: log.html shows the repeated low-level call sequence twice.

3. Extract the first user keyword without changing behavior

Replace the repeated normalization steps with a user keyword. Keep the implementation in the same file for now so Chapter 12 resource-file architecture is not required yet.

*** Keywords ***
Normalize Release Id
    [Arguments]    ${raw}
    ${lower}=    Convert To Lower Case    ${raw}
    ${slug}=    Replace String    ${lower}    ${SPACE}    -
    RETURN    ${slug}

Then update both tests to call Normalize Release Id. Re-run to evidence/extracted. The expected observable labels and PASS/FAIL outcomes must be unchanged, while the log hierarchy should now show a reusable user-keyword node containing the String-library calls.

4. Add required and default arguments where ownership is clear

The next duplication is label construction. A component name is required; a separator has a safe, stable default. Model those two facts directly.

Build Release Label
    [Arguments]    ${component}    ${slug}    ${separator}=:
    ${label}=    Catenate    SEPARATOR=${separator}    ${component}    ${slug}
    RETURN    ${label}

*** Test Cases ***
API Release Label Is Stable
    ${slug}=    Normalize Release Id    API Candidate 42
    ${label}=    Build Release Label    api    ${slug}
    Should Be Equal    ${label}    api:api-candidate-42

Custom Separator Is Explicit
    ${slug}=    Normalize Release Id    UI Candidate 42
    ${label}=    Build Release Label    ui    ${slug}    separator=/
    Should Be Equal    ${label}    ui/ui-candidate-42

The named override makes the call self-documenting. A reader does not have to remember what the third positional cell means.

5. Use named-only arguments for options that should never be cryptic

Suppose label generation can optionally include a synthetic environment. Passing a Boolean by position would produce calls such as Build ... True, which conceal meaning. Make the option named-only instead.

Build Release Label
    [Arguments]    ${component}    ${slug}    @{}    ${environment}=local
    ${label}=    Catenate    SEPARATOR=:    ${environment}    ${component}    ${slug}
    RETURN    ${label}

*** Test Cases ***
Named-only option is readable
    ${slug}=    Normalize Release Id    API Candidate 42
    ${label}=    Build Release Label    api    ${slug}    environment=qa
    Should Be Equal    ${label}    qa:api:api-candidate-42

The lone @{} marks subsequent normal arguments as named-only. This is an interface decision, not a formatting trick.

6. Add stable type conversion only where it improves the contract

A count-like input is a good place to use stable 7.3+ user-keyword type conversion. Conversion failure occurs before the keyword body mutates anything.

Repeat Label
    [Arguments]    ${label}    ${count: int}=1
    Should Be True    ${count} > 0
    ${result}=    Evaluate    [$label] * $count
    RETURN    ${result}

Why this example is contained. Evaluate operates only on already-created synthetic in-memory values here. Do not use expression evaluation to ingest untrusted production input or as a substitute for a domain-specific library API.

Call Repeat Label demo 3 and inspect that the keyword receives an integer. Then call it with oops in a deliberately failing test: the conversion error proves an interface failure before the body executes.

7. Varargs and free-named arguments are forwarding tools, not default design

Use variable argument forms when the capability is genuinely open-ended. The following tiny formatting helper accepts any number of labels. A wrapper that forwards extensible named options can use &{options}, but broad forwarding should remain uncommon in domain keywords because it exposes a weaker contract.

Join Labels
    [Arguments]    @{labels}
    ${joined}=    Catenate    SEPARATOR= |    @{labels}
    RETURN    ${joined}

Forward To Catenate
    [Arguments]    @{values}    &{options}
    ${joined}=    Catenate    @{values}    &{options}
    RETURN    ${joined}

Notice the ownership difference: Join Labels defines a meaningful fixed behavior, while Forward To Catenate mostly re-exports another keyword. The second form is justified only if your project genuinely needs a controlled adapter.

8. Return multiple values only when they form one coherent result

Multiple returns are useful when one operation naturally yields a small fixed tuple. Do not return a shifting number of fields based on success/failure.

Split Release Identity
    [Arguments]    ${identity}
    ${component}    ${version}=    Split String    ${identity}    :    1
    RETURN    ${component}    ${version}

*** Test Cases ***
Caller owns both returned fields
    ${component}    ${version}=    Split Release Identity    api:42
    Should Be Equal    ${component}    api
    Should Be Equal    ${version}    42

Because the return shape is fixed, the caller can reason about it. A keyword that sometimes returns one value, sometimes three, and sometimes an empty string turns runtime state into an undocumented protocol.

9. Inspect the caller-facing interface with Libdoc

Generate interface evidence from the refactored suite instead of relying on the source alone.

python -m robot.libdoc release_refactored.robot list
python -m robot.libdoc release_refactored.robot show "Build Release Label"
python -m robot.libdoc release_refactored.robot evidence/keywords.html

Verify that the displayed signatures preserve required/default/named-only/type information. Libdoc does not prove behavior correctness; it proves what interface metadata Robot exposes to callers.

10. Compare before/after evidence

Run the refactored suite into its own directory:

python -m robot --outputdir evidence/refactored release_refactored.robot

Compare these observations:

Evidence Flat version Refactored version
Test outcomes Expected PASS Same expected PASS results.
Repeated low-level steps Visible directly under each test Nested under reusable keyword nodes.
Caller-owned input Scattered literal sequence knowledge Explicit argument cells.
Returned data Intermediate variables created by copied steps Explicit RETURN values.
Diagnosis path Search repeated lines Open domain/helper node, then failing nested call.
Interface documentation None beyond test source Libdoc signatures and keyword documentation available.

11. Challenge: choose the correct interface, do not copy a sequence

Create a keyword named Build Candidate Summary that receives a required component, a required release identifier, and a named-only environment defaulting to local. It should call Normalize Release Id and Build Release Label, then return the summary string. Do not use suite/global variables.

Before implementing, predict: (1) which values belong to the caller; (2) which values are local to each helper; (3) what exact value is returned; and (4) where a String-library failure would appear in the log hierarchy.

12. Verification and cleanup

  • The flat and refactored baseline tests have the same outcomes.
  • All Web/API/database/browser state remains untouched because the lab is synthetic and local.
  • Required/default/named-only arguments appear as intended in Libdoc.
  • The typed-argument failure is preserved in a separate result directory.
  • log.html shows high-level user keywords with nested implementation calls.
  • No keyword relies on suite/global mutation for normal data flow.

If deleting the lab, move to its parent directory and remove only the known rf-keyword-lab directory after inspecting the path. The result folders are useful input for Lesson 4, so preserving them is often better than immediate deletion.

13. Knowledge check

Why did the behavior proof require running both the flat and refactored versions?

Why is environment=qa preferable to an unlabeled positional qa for a named-only option?

When is &{options} a good fit?

What does Libdoc prove in this workflow?

If typed argument conversion fails, should you inspect the external system first?

14. Summary and next step

You converted duplicated procedural steps into explicit keyword interfaces: callers own scenario inputs; keywords own implementation; defaults and named-only options encode design choices; typed arguments can reject invalid inputs early; RETURN makes output ownership explicit; and Libdoc/log evidence reveals both the interface and nested execution.

Lesson 3 asks where to stop abstracting and compares explicit arguments, configuration objects, return values, shared mutation, wrappers, domain names, and deep keyword chains.

Next lesson

Keywords, Arguments, Return Values, and Reusable Abstractions: Configuration, Design Patterns, and Trade-Offs

Continue with Keywords, Arguments, Return Values, and Reusable Abstractions: 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.