🔥 0
0
Lesson 5 of 10 25 min +200 XP

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>
PropertyValuePurpose
Filenameusers.csvPath to CSV file
Variable Namesemail,password,firstNameColumn-to-variable mapping
Ignore First LinetrueSkip header row
Recycle on EOFtrue/falseRestart from beginning when file ends
Stop Thread on EOFfalseWhat to do when data runs out
Sharing ModeAll threadsHow 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.

🧠 Quick Quiz

Test your understanding of this lesson.

1

What is the purpose of CSV Data Set Config?

2

What does 'Sharing mode: All threads' mean?

3

Why should you use 'Recycle on EOF: False' for user credentials?