Parameterization & Data-Driven Tests
Hardcoded test data is a trap. When you test checkout with the same user and same product 1,000 times, you're not simulating reality. Real e-commerce has thousands of users buying thousands of products.
Parameterization separates your test logic from test data, enabling realistic, data-driven performance tests.The Problem with Hardcoded Data
<!-- Bad: Same user, same product, every iteration -->
<HTTPSamplerProxy testname="Login">
<stringProp name="Argument.value">{"email":"test@example.com","password":"test123"}</stringProp>
</HTTPSamplerProxy>
<HTTPSamplerProxy testname="Add to Cart">
<stringProp name="HTTPSampler.path">/api/cart/add/PROD-001</stringProp>
</HTTPSamplerProxy>
Problems:
- Server may cache results for repeated identical requests
- Same user session conflicts across threads
- Database hotspots on same product row
- Doesn't test inventory logic with different products
CSV Data Set Config
The most powerful parameterization tool in JMeter. Reads data from external CSV files.
Create Your Data File
users.csv:
email,password,firstName
john@example.com,john123,John
jane@example.com,jane456,Jane
mike@example.com,mike789,Mike
sara@example.com,sara012,Sara
products.csv:
productId,productName,expectedPrice
PROD-001,Wireless Headphones,79.99
PROD-002,USB-C Hub,49.99
PROD-003,Mechanical Keyboard,129.99
PROD-004,4K Monitor,399.99
PROD-005,Laptop Stand,34.99
Configure CSV Data Set
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet"
testname="User Data">
<stringProp name="filename">users.csv</stringProp>
<stringProp name="fileEncoding">UTF-8</stringProp>
<stringProp name="variableNames">email,password,firstName</stringProp>
<stringProp name="delimiter">,</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
</CSVDataSet>
| Property | Value | Purpose |
|---|---|---|
| Filename | users.csv | Path to CSV file |
| Variable Names | email,password,firstName | Column-to-variable mapping |
| Ignore First Line | true | Skip header row |
| Recycle on EOF | true/false | Restart from beginning when file ends |
| Stop Thread on EOF | false | What to do when data runs out |
| Sharing Mode | All threads | How threads share data |
Use Variables in Requests
<HTTPSamplerProxy testname="Login">
<stringProp name="HTTPSampler.method">POST</stringProp>
<stringProp name="HTTPSampler.path">/api/auth/login</stringProp>
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<stringProp name="Argument.value">{
"email": "${email}",
"password": "${password}"
}</stringProp>
</HTTPSamplerProxy>
<HTTPSamplerProxy testname="Add Product to Cart">
<stringProp name="HTTPSampler.method">POST</stringProp>
<stringProp name="HTTPSampler.path">/api/cart/items</stringProp>
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<stringProp name="Argument.value">{
"productId": "${productId}",
"quantity": 1
}</stringProp>
</HTTPSamplerProxy>
Sharing Modes Explained
All Threads (Default)
All threads share one file cursor. Each thread gets the next row.
Thread 1: Gets row 1 (john@example.com)
Thread 2: Gets row 2 (jane@example.com)
Thread 3: Gets row 3 (mike@example.com)
Thread 1 (iteration 2): Gets row 4 (sara@example.com)
Use for: Unique user credentials, order numbers, anything that shouldn't repeat.
Current Thread Group
Each thread group has its own cursor. Threads within a group share.
Thread Group 1:
Thread 1: Gets row 1
Thread 2: Gets row 2
Thread Group 2:
Thread 1: Gets row 1 (same as TG1-T1!)
Thread 2: Gets row 2 (same as TG1-T2!)
Use for: When different thread groups need independent data sets.
Current Thread
Each thread has its own cursor. Every thread reads the entire file independently.
Thread 1: Gets row 1, then row 2, then row 3...
Thread 2: Gets row 1, then row 2, then row 3...
Use for: When you want every thread to cycle through all test data.
User Defined Variables
For configuration values that don't change during test execution.
<Arguments guiclass="ArgumentsPanel" testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments">
<elementProp name="BASE_URL" elementType="Argument">
<stringProp name="Argument.name">BASE_URL</stringProp>
<stringProp name="Argument.value">https://api.mystore.com</stringProp>
</elementProp>
<elementProp name="API_VERSION" elementType="Argument">
<stringProp name="Argument.name">API_VERSION</stringProp>
<stringProp name="Argument.value">v2</stringProp>
</elementProp>
<elementProp name="TIMEOUT" elementType="Argument">
<stringProp name="Argument.name">TIMEOUT</stringProp>
<stringProp name="Argument.value">30000</stringProp>
</elementProp>
</collectionProp>
</Arguments>
Use in requests:
<HTTPSamplerProxy testname="Get Products">
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
<stringProp name="HTTPSampler.path">/api/${API_VERSION}/products</stringProp>
</HTTPSamplerProxy>
Environment-Specific Config
Pass variables from command line for different environments:
# Staging
jmeter -n -t test.jmx -JBASE_URL=https://staging-api.mystore.com
# Production
jmeter -n -t test.jmx -JBASE_URL=https://api.mystore.com
Access with ${__P(BASE_URL)} function.
Complete E-commerce Data Setup
File Structure
test-data/
├── users.csv (1000 unique users)
├── products.csv (500 products)
├── addresses.csv (user shipping addresses)
├── payment_methods.csv (test credit cards)
└── promo_codes.csv (discount codes)
users.csv
email,password,firstName,lastName,userId
user001@test.com,Pass001!,John,Smith,USR001
user002@test.com,Pass002!,Jane,Doe,USR002
user003@test.com,Pass003!,Bob,Johnson,USR003
products.csv
productId,sku,category,price,stockLevel
PROD-001,SKU-WH-001,Electronics,79.99,500
PROD-002,SKU-KB-002,Electronics,129.99,200
PROD-003,SKU-SH-003,Footwear,89.99,1000
Test Plan Configuration
<!-- User data - unique per thread, don't recycle -->
<CSVDataSet testname="User Data">
<stringProp name="filename">test-data/users.csv</stringProp>
<stringProp name="variableNames">email,password,firstName,lastName,userId</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="recycle">false</boolProp>
<boolProp name="stopThread">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
</CSVDataSet>
<!-- Product data - can recycle, shared across threads -->
<CSVDataSet testname="Product Data">
<stringProp name="filename">test-data/products.csv</stringProp>
<stringProp name="variableNames">productId,sku,category,price,stockLevel</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="recycle">true</boolProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
</CSVDataSet>
Random Selection Functions
Random Number
<!-- Random quantity between 1 and 5 -->
<stringProp name="Argument.value">{
"productId": "${productId}",
"quantity": ${__Random(1,5)}
}</stringProp>
Random from List
// JSR223 PreProcessor
def categories = ["Electronics", "Clothing", "Home", "Sports"]
def randomCategory = categories[new Random().nextInt(categories.size())]
vars.put("randomCategory", randomCategory)
Random Product from CSV
Use __CSVRead function for random access:
<!-- Read random row from products.csv -->
<stringProp name="HTTPSampler.path">/api/products/${__CSVRead(products.csv,0)}</stringProp>
Data Generation with Groovy
For dynamic data that doesn't come from files:
// JSR223 PreProcessor - Generate realistic order data
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
// Random order reference
def orderRef = "ORD-" + System.currentTimeMillis() + "-" + Thread.currentThread().getId()
vars.put("orderRef", orderRef)
// Future delivery date (3-7 days from now)
def deliveryDays = new Random().nextInt(5) + 3
def deliveryDate = LocalDateTime.now().plusDays(deliveryDays)
vars.put("deliveryDate", deliveryDate.format(DateTimeFormatter.ISO_DATE))
// Random shipping option
def shippingOptions = ["standard", "express", "overnight"]
def shipping = shippingOptions[new Random().nextInt(shippingOptions.size())]
vars.put("shippingOption", shipping)
log.info("Generated order: ${orderRef}, delivery: ${vars.get('deliveryDate')}")
Parallel-Safe Data Strategies
Problem: Data Collisions
100 threads using 50 products = guaranteed collisions. Two threads might:
- Add same product to cart simultaneously
- Both try to buy last item in stock
- Cause race conditions in inventory
Solution 1: Unique Data Per Thread
Ensure data file has at least threads * iterations unique rows:
Threads: 100
Iterations: 10
Required rows: 1000 unique users
<CSVDataSet testname="Unique Users">
<boolProp name="recycle">false</boolProp>
<boolProp name="stopThread">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
</CSVDataSet>
Solution 2: Thread-Specific Data Files
Split data into per-thread files:
users_thread1.csv
users_thread2.csv
...
users_thread100.csv
// JSR223 PreProcessor
def threadNum = ctx.getThreadNum()
def filename = "test-data/users_thread${threadNum}.csv"
vars.put("userDataFile", filename)
Solution 3: Counter for Uniqueness
Use JMeter's Counter element:
<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig"
testname="Order Counter">
<stringProp name="CounterConfig.start">1</stringProp>
<stringProp name="CounterConfig.end">999999</stringProp>
<stringProp name="CounterConfig.incr">1</stringProp>
<stringProp name="CounterConfig.name">orderNumber</stringProp>
<stringProp name="CounterConfig.format">ORD-000000</stringProp>
<boolProp name="CounterConfig.per_user">false</boolProp>
</CounterConfig>
per_user=false means all threads share one counter (guaranteed unique).
Key Takeaways
- CSV Data Set Config is essential for realistic tests with varied data.
- Sharing mode matters: Use "All threads" for unique data, "Current thread" when each thread needs full data set.
- Plan your data: Ensure enough unique rows for threads * iterations.
- Generate dynamic data: Use Groovy for order numbers, timestamps, and random selections.
Next Lesson
Your tests now use realistic, varied data. But how do you know if responses are correct? A 200 status code doesn't mean success if the response body contains an error message. Next: Assertions - validating that your application actually works under load.