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.
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
RETURNand verify caller ownership. -
Use Libdoc and
log.htmlas 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.htmlshows 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?
A refactor is successful only if the externally observable test behavior remains equivalent while structure improves. Source appearance alone is not evidence.
Why is environment=qa preferable to an unlabeled
positional qa for a named-only option?
The call documents which option is being changed and prevents accidental positional binding.
When is &{options} a good fit?
When a genuine adapter/wrapper must accept and forward extensible named arguments. It should not be used merely to avoid defining a domain interface.
What does Libdoc prove in this workflow?
It proves the caller-facing keyword metadata/signature Robot exposes; it does not prove runtime correctness.
If typed argument conversion fails, should you inspect the external system first?
No. The failure occurs at the keyword interface before the body executes, so inspect the supplied value and signature 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.
Further reading
- Robot Framework 7.4.2 User Guide — Creating user keywords — keyword syntax, arguments, embedded arguments, return values, setup/teardown, privacy, and recursion.
- Robot Framework 7.4.2 User Guide — Handling keywords with same names — resolution scope, explicit qualification, and search order.
- Robot Framework 7.4.2 User Guide — Libdoc — generating user/library documentation and inspecting keyword signatures.
- RFCP syllabus — User Keyword Definition & Arguments — keyword naming, interfaces, arguments, and maintainable design.
- Robot Framework Style Guide — naming, abstraction level, keyword design, and maintainability guidance.
- Robot Framework 7.4.2 release notes — current stable-line changes and deprecations.
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.