Chapter 20Lesson 03~185 minutes

Functions, Custom Properties, Reusable Fragments, and Modular Plans: Configuration, Design Patterns, and Trade-Offs

Reuse is beneficial only when the boundary represents stable behavior. Over-modularization can be as hard to understand as duplication: a single request may require following five includes, twelve properties, and nested functions before the effective workload is visible. The design goal is not maximum reuse; it is minimum duplication with explicit ownership and predictable runtime assembly.

Design boundariesModule vs IncludePath strategyDeterministic dataCI portability

Learning objectives

  • Choose function, variable, or property based on ownership and evaluation time.
  • Choose Module or Include Controller based on same-plan versus cross-plan reuse.
  • Choose one JMX or several modules based on dependency and release boundaries.
  • Choose relative paths or explicit project-root resolution deliberately.
  • Choose generated random data or committed/generated deterministic fixtures from replay needs.
  • Connect modular choices to configured/achieved workload and generator/target evidence.

1. Mandatory path stays local/free

Runnable examples remain 127.0.0.1:8020, ≤2 threads ×3 work loops. Managed load clouds, shared performance environments, paid CI, enterprise identity, Kubernetes, remote RMI, external APIs, and third-party plugins are optional contexts only.

2. Function versus variable versus property

Need Prefer Reason
Read a run/load knob in a field __P / property Process-wide configuration supplied from property file or -J.
Store response/session/user state variable Thread-local ownership and ordinary extractor/config semantics.
Compute small context-dependent value built-in function No extra component/code when evaluation semantics are clear.
Reuse the computed value many times function with reference variable or explicit producing element Avoid repeating an opaque function expression in many fields.
Change a property during run rare/special only Properties are global; runtime mutation can create races and hide configuration history.

3. Module Controller versus Include Controller

Module Controller Include Controller
Targets a uniquely named controller already loaded in the same JMX. Loads an external JMX/Test Fragment.
Best for reuse between scenarios inside one plan. Best for reuse across multiple top-level plans/projects.
No external file deployment/path problem. Adds file deployment, prefix/path, version and CI/remote-engine concerns.
Reference can be selected from controllers loaded in GUI. Filename is fixed text; no variables/functions in Filename field.
Fragment naming uniqueness is critical. Include Controller naming/path uniqueness and included fragment structure are critical.

Start with Module when reuse is local to one plan. Move to Include when independent reuse justifies the extra file/path/dependency contract.

4. One JMX versus several modules

One JMX has fewer deployment/path problems and is easier for a beginner to trace. It becomes awkward when many scenarios duplicate a stable flow or teams need separate ownership/release cadence.

Several JMX modules reduce definition duplication and permit targeted ownership, but require dependency naming, file deployment, path resolution, and compatibility/version policy. Keep the module graph shallow and acyclic.

5. Relative paths versus explicit project-root resolution

Relative path only Project-root launcher / explicit prefix
Short and convenient when cwd/JMX launch path never changes. Stable across arbitrary caller working directories.
Can break in CI/IDE/scheduler/remote engine when launch location differs. Requires one launcher/config contract but makes resolved paths observable.
Failure may appear only after plan is moved. Manifest can record exact project root/prefix before JMeter starts.

The Chapter 20 mandatory solution uses an explicit launcher-derived absolute includecontroller.prefix. The JMX keeps only the stable filename session-bootstrap.jmx.

6. Property files versus command-line run overrides

Use a committed synthetic environment property file for stable defaults such as localhost host/port and conservative load ceilings. Use explicit -J values for run identity/variant or intentional one-off overrides.

Do not place real credentials in either. JMeter property files and command manifests are ordinary files and can be committed/artifacted accidentally.

7. Explicit defaults versus fail-fast configuration

An explicit safe default makes a lab runnable. Production/release plans sometimes need a stricter contract where a missing property should fail before traffic. Since __P can silently return 1 when no default is supplied, use one of these patterns:

  • always specify an explicit conservative default and record resolved values;
  • use a one-time preflight assertion/script to reject missing/unsafe properties before Thread Groups start;
  • have the launcher validate required keys before invoking JMeter.

8. Generated data versus committed synthetic fixtures

Committed small synthetic fixtures are easy to review and reproduce. Generated data is useful when the dataset is large or must vary structurally, but the generator/version/seed/manifest becomes part of evidence.

For exact replay, prefer deterministic generation or a committed/generated manifest rather than unseeded random functions.

9. Randomness needs a reproducibility strategy

__Random(min,max) returns a pseudo-random number but exposes no seed parameter. If exact numeric sequence matters, the current Random Variable Config Element offers a Random Seed and Per Thread option. Alternatively, pre-generate a seeded CSV dataset outside load execution.

__UUID() is excellent for unique correlation when exact replay is not required; do not confuse uniqueness with reproducibility.

10. Keep function use readable

Prefer:

Property-backed defaults:
HOST = ${__P(target.host,127.0.0.1)}
PORT = ${__P(target.port,8020)}
RUN_ID = ${__P(run.id,p20-local)}

Sampler:
user=user-${__threadNum}
run_id=${__P(run.id,p20-local)}

over a single field containing nested conditionals, random values, time functions, properties and string concatenation. If a reviewer cannot predict the effective request without executing the expression, the plan is too opaque.

11. Fragment input/output contract

FRAG.session.bootstrap.v1 owns:

  • input: target defaults already available from top-level plan; run.id/variant properties; current __threadNum;
  • action: one POST session-create request;
  • output: thread-local SESSION_ID;
  • assertion: status created and SESSION_ID exists;
  • does not own: thread count, pacing, Cookie Manager, run result files, cleanup, or global mutable properties.

This contract keeps the fragment reusable without hiding workload control.

12. Configuration-layer boundaries

Layer Examples Do not confuse with
JMeter core/test plan functions, properties, Test Fragment, Module/Include, variables, controllers Java/JVM user.dir or OS filesystem permissions.
Java/JVM user.dir, heap/GC, Java version Include controller business semantics.
OS/filesystem/network path separators, cwd, file permissions, loopback sockets JMeter property/variable ownership.
SUT session/work behavior, service timing Module definition duplication or include path errors.
Plugin/driver none in mandatory lab core Module/Include/functions.
CI/container checkout path, working directory, mounted modules, workspace production target capacity.

13. Distributed-run implication

Remote engines do not share the controller's filesystem. External fragments, property files, and data required by a remote plan must exist consistently on every engine, and JVM-global properties are global only within each engine. Chapter 20 does not run RMI; it teaches the dependency contract now so Chapter 25 can apply it safely.

14. Worked scenario

Four top-level plans share a 6-step authentication bootstrap, but only two use the same cookie/config assumptions.

  • Do not make one giant fragment controlled by many Boolean properties.
  • Create a stable common bootstrap module only if its inputs/outputs truly match.
  • Keep top-level Cookie Manager/UDVs/environment config in each main plan.
  • Use Include for cross-plan reuse, unique names, explicit prefix/deployment policy.
  • Keep plan-specific assertions/branch logic near the callers.

15. Decision table

Requirement Preferred choice Evidence/validity
Same flow reused twice in one JMX Test Fragment + Module Controller No external path; same target/sample counts.
Same stable flow reused by many plans External Test Fragment + Include Controller Module version/path/dependency recorded.
Per-user session ID variable Thread isolation visible in target events.
Host/threads/loops property + __P Resolved property manifest before load.
Exact replay of random numeric data seeded Random Variable or pre-generated fixture Seed/manifest retained.
Portable CI/local execution project launcher + absolute resolved prefix Two-cwd/clean-shell proof.

16. Configured versus achieved load

Configured workload is resolved thread/loop/pacing properties plus runtime controller placement. Achieved workload is the successful session-create/work/delete sample count and target event distribution. A modular refactor is performance-valid only when generator path/module resolution succeeds and the target workload remains equivalent. Preserve JTL + matching jmeter.log, properties, command manifest, fragment graph, resolved prefix, generator CPU/heap, target events, and duplication note.

Knowledge check

When is Module Controller preferable to Include Controller?

What makes an external module a CI/distributed dependency?

How can exact random replay be improved?

Why shouldn't a fragment own thread count/pacing?

What distinguishes configured from achieved workload after modularization?

Next lesson

Diagnose path, property, naming and dependency failures

Lesson 4 deliberately breaks Include path resolution and covers hidden defaults, duplicate names, global property state, random reproducibility and module-graph failures.

Official references and version notes

Version and compatibility note

Version-sensitive statements were rechecked against current Apache JMeter primary documentation on 2026-09-05. The course baseline remains Apache JMeter 5.6.3 with a Java 17 JDK; JMeter 5.6.3 requires Java 8+. A Module Controller substitutes an already loaded controller/fragment into the active runtime path; fragments referenced by Module Controller need unique names because JMeter uses the controller/parent name path to find them after reload. Include Controller is for external JMX/Test Fragment content. Its Filename field does not support variables/functions. The includecontroller.prefix property can prefix the filename; if prefix+filename cannot be found, JMeter attempts the filename relative to the JMX launch directory. Top-level Cookie Manager/User Defined Variables belong in the main test plan rather than an included JMX because included copies are not guaranteed to work as expected. __P(name,default) is intended for command-line properties; without an explicit default it returns 1. __threadNum is local to its Thread Group and should not be placed in Configuration Elements such as User Defined Variables because those are processed by a separate thread. For reproducible random numeric data, the Random Variable Config Element exposes a seed/per-thread option; the simple __Random function does not expose an equivalent seed parameter.

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.