Chapter 20Lesson 01~160 minutes

Functions, Custom Properties, Reusable Fragments, and Modular Plans: Core Concepts and Mental Model

Chapter 19 reduced hidden scripting by pushing standard behavior back into the JMeter tree. The next maintainability problem appears when a test plan grows: the same host, thread counts, setup/login sequence, assertions, and helper requests are copied into multiple scenarios. Copy/paste initially feels simple, but the copies drift. One scenario receives a timeout fix, another keeps an old path, and a CI run silently uses different defaults. Chapter 20 treats a JMeter project as code: configuration is externalized, reusable flows have named boundaries, paths are resolved deliberately, and the final runtime tree still makes workload semantics visible.

__P / propertiesTest FragmentModule ControllerInclude ControllerPortable paths

Learning objectives

  • Explain how functions, properties, Test Fragments, Module Controller, and Include Controller fit into JMeter execution.
  • Distinguish process-wide properties, thread-local variables, function evaluation, external files, and target/session state.
  • Understand same-plan Module references versus external Include imports.
  • Recognize path resolution and module naming as runtime correctness concerns.
  • Inspect project/module/property state non-destructively before refactoring.
  • Keep modularity visible enough that configured and achieved workload remain reviewable.

1. The practical problem: duplication becomes configuration drift

Imagine three scenarios that each copy a four-element “session bootstrap” sequence. When the endpoint changes, twelve tree nodes must be reviewed. If one copy keeps the old path or assertion, the test suite no longer represents one workload definition. A modular plan should reduce the number of definitions while preserving the number and order of runtime operations.

Mandatory chapter boundary: only the disposable fixture at http://127.0.0.1:8020, synthetic user/session identifiers, maximum 2 threads ×3 work loops, 25 ms pacing, no credentials, no plugins, no remote engines, and no external target. Never substitute a public/shared/production endpoint for the modularity lab.

2. Mental model: project sources assemble into an ordinary thread-local runtime tree

Modular project sources to runtime execution

Modularity changes where configuration is defined, not the fundamental JMeter execution model. Properties/functions resolve configuration, controllers assemble reusable elements, and the resulting samplers still execute inside ordinary JMeter threads against the same authorized target.

flowchart TD
P[Property files + -J run settings] --> R[Functions such as __P / __property]
F[Test Fragment definitions] --> M[Module Controller references]
X[External fragment JMX] --> I[Include Controller + include prefix]
R --> A[Assembled JMeter runtime tree]
M --> A
I --> A
A --> T[Thread-local execution: vars / sessions / counters]
T --> H[HTTP samplers to 127.0.0.1:8020]
H --> J[JTL + jmeter.log]
H --> E[Fixture session/work event evidence]
G[Generator cwd / files / CPU / path state] --> V[Validity review]
J --> V
E --> V

The property layer supplies environment/run configuration. Functions such as __P read those properties when JMeter evaluates fields. A Module Controller points to a controller/fragment already loaded into the same JMX. An Include Controller loads an external JMX Test Fragment. These sources are assembled into the effective runtime tree. Once execution begins, each JMeter thread still owns its ordinary variables, HTTP/session state, counters, and samples. Modular structure therefore changes code organization and configuration lookup—not the fundamental thread/workload model. JTL/fixture events prove what actually executed, while generator path/file state proves that the intended modules were loaded.

3. Function versus variable versus property

Mechanism Ownership / evaluation Use in this chapter
JMeter function Evaluated where the containing field is processed. __P reads run/load settings; __threadNum creates a per-thread synthetic user name.
Variable ${{NAME}} Thread-local JMeterVariables after the variable-producing element runs. SESSION_ID, sequence counters, extracted response state.
Property Shared within one JMeter JVM/process. Target host/port, threads, loops, pacing, RUN_ID/VARIANT; immutable during the run.
External data/file Filesystem state visible to the injector. Property file, external fragment JMX, optional committed synthetic fixtures.

Properties are configuration. Variables are virtual-user state. Functions are evaluation mechanisms. Mixing those roles—such as putting SESSION_ID in a property—creates cross-thread contamination.

4. __P is convenient—and its default matters

${__P(target.host,127.0.0.1)} returns the target.host JMeter property or the explicit fallback. Current JMeter defines an omitted __P default as 1. That makes ${__P(threads)} syntactically valid even when the property was forgotten—which can silently turn a planned 20-thread run into one thread.

For operational plans, use explicit safe defaults and preflight the resolved property set instead of relying on hidden fallback behavior.

5. __property is useful for inspection

${__property(user.dir,JMETER_USER_DIR)} reads the JVM user.dir property and can also store it into a JMeter variable. This is useful in a one-thread debug/preflight element to prove the current working directory/path context. Do not add a debug sampler to the measured workload merely to collect a value that the launcher can record once.

6. __threadNum belongs in thread execution, not Configuration Elements

${__threadNum} returns a number local to the containing Thread Group. Use it directly in the bootstrap sampler field to create user-1, user-2, and so on. Current JMeter explicitly warns that __threadNum does not work correctly inside Configuration Elements such as User Defined Variables because those are processed by a separate thread.

7. Module Controller: reuse inside one loaded plan

A Module Controller substitutes a selected controller/fragment already loaded in the JMeter GUI/test plan. The target fragment can live under a Test Fragment or disabled/dummy Thread Group. The module reference is convenient when several scenarios inside one JMX need the same sequence.

Fragment/controller names must be unique enough for JMeter to find the correct path after reload. Avoid multiple generic names such as “Simple Controller” or “Login.” Use names such as FRAG.session.bootstrap.v1.

8. Include Controller: reuse across JMX files

Include Controller loads an external JMX that contains a Test Fragment. A debug Thread Group may also be present in that external file, but JMeter ignores it during include. This makes Include appropriate when the same fragment must be versioned independently and reused by several top-level plans.

Important current constraints:

  • the Include filename field does not support variables/functions;
  • includecontroller.prefix can supply a common prefix/path;
  • if prefix+filename is not found, JMeter attempts the filename relative to the JMX launch directory;
  • Cookie Manager/User Defined Variables should stay in the top-level plan rather than the included file.

9. Test Fragment is a definition boundary, not a workload by itself

Test Fragment is designed for Module/Include use. Since JMeter 2.13, a Test Fragment used with Module Controller is disabled by default so it is not executed independently and again through the Module reference. The definition should contain only the reusable sequence and assumptions it owns; environment-wide configuration stays outside it.

10. State checklist before modularizing

State Question
Generator JMeter/Java version; cwd/user.dir; project root; accessible fragment/property files; CPU/heap?
Thread/arrival Threads/loops/pacing unchanged by the refactor? Which controller runs once versus per loop?
Component scope Where do defaults, fragments, Module/Include Controllers, assertions, timers and cleanup sit?
Variables/properties Which values are per-user vars and which are immutable JVM properties?
Protocol/session Does the fragment create one session per thread, and is cleanup after the work loop?
Target Same localhost endpoint/work distribution before/after modularization?
Files/paths Exact main JMX, fragment JMX, property file, launcher, cwd and include prefix?
Artifacts JTL, jmeter.log, command manifest, target events, dependency/duplication note?
Trust/privacy No secrets in property files, manifests, JMX or logs?
Validity Did organization change only definitions, not achieved work/sample counts?

11. Non-destructive project inspection first

Before changing a JMX, inventory reusable candidates and path dependencies:

PowerShell:

Get-ChildItem -Recurse -File . |
  Select-Object FullName, Length

Get-ChildItem -Recurse -Filter *.jmx |
  Select-String -Pattern "TestFragmentController|ModuleController|IncludeController|__P|__Random|__UUID"

Get-Content .\config\local.properties

Also inspect one copy of each duplicated setup flow in the GUI: parent scope, HTTP Defaults/Cookie Manager, extractors/assertions, and every variable it produces. A fragment is safe only when its required inputs and outputs are understood.

12. Draw the module dependency graph before extraction

For the mandatory lab the graph is intentionally acyclic:

main-module.jmx
  └── Module Controller -> FRAG.session.bootstrap.v1 (same JMX)

main-include.jmx
  └── Include Controller -> session-bootstrap.jmx
                               └── Test Fragment -> FRAG.session.bootstrap.v1

No fragment includes the main plan and no fragment includes itself. A dependency graph that cannot be described simply is already a maintainability warning.

13. Runtime order must remain visible

The target workload for every variant is:

Per thread:
1. Create one synthetic session (once)
2. Repeat WORK exactly ${__P(loops,3)} times with pacing
3. Delete exactly that thread's SESSION_ID

Modularity is green only if the same sequence and counts are observed in JTL and fixture events.

14. DevOps connection

A modular JMeter project can be reviewed like application code: property contracts are diffable, fragment dependencies are named, launcher behavior is reproducible, paths are testable in CI, and duplication is measurable. This makes performance plans maintainable assets rather than GUI documents that only work on the author's workstation.

Knowledge check

What is the core difference between Module and Include Controller?

Why is __P(threads) without a default risky?

Why should SESSION_ID remain a variable?

Why can Include Controller become cwd/path brittle?

What proves modularization did not alter workload semantics?

Next lesson

Refactor duplicated setup into a portable modular project

Lesson 2 builds the localhost fixture, property file, Module and Include variants, launcher, two-working-directory proof, and a deliberate path failure.

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.