Test Data
A test is only as good as the data you feed it. The same login test can pass with "Priya" and fail with "D'Souza". Test data is every input value, account, file and database record you prepare so that tests can run and reveal problems.
In this lesson you will learn the kinds of test data you need, where test data comes from, how to generate repeatable synthetic data, why real customer data must be masked, and how a small list of tricky inputs finds bugs quickly.
Kinds of test data
| Kind | Purpose | Example for a signup form |
|---|---|---|
| Valid | Check the normal path works | meera@example.com, age 24 |
| Invalid | Check bad input is refused | meera@, age -3 |
| Boundary | Check the exact edges of rules | age 17, 18, 60, 61 when the rule is 18 to 60 |
| Empty and missing | Check required fields | empty email, spaces only |
| Special characters | Check text handling | D'Souza, Mohan-Lal, names in Hindi |
| Large volume | Check limits and performance | 10,000 orders, a 300-character address |
| State data | Put the system in a known state | a locked account, an expired coupon, an empty cart |
You will learn how to choose valid, invalid and boundary values systematically in Equivalence Partitioning & Boundary Values.
Where test data comes from
- Created by hand. Small, precise sets written into test cases. Best for specific checks.
- Generated (synthetic). Created by a script or a library such as Faker. Best for volume and variety, and it contains no real person's details.
- Copied from production. Realistic, but it contains real customers' personal data. It must be masked or anonymised before it reaches a test environment, and many teams avoid it completely.
Generating repeatable synthetic data
Random data is useful because it finds cases you did not think of. But if a test fails with random data, you must be able to reproduce it. The trick is a seed: give the random number generator the same starting value and it produces the same "random" sequence every time.
import random
random.seed(42) # same seed, same data on every run
FIRST = ["Aarav", "Priya", "Kabir", "Meera", "Rohan", "Ananya", "Farhan", "Isha"]
CITIES = ["Rohtak", "Jaipur", "Pune", "Kochi", "Lucknow"]
def make_customer(n):
name = random.choice(FIRST)
return {
"id": f"CUST-{n:03d}",
"name": name,
"email": f"{name.lower()}{n}@example.com",
"city": random.choice(CITIES),
"wallet": random.randint(0, 5000),
}
customers = [make_customer(n) for n in range(1, 6)]
for c in customers:
print(c){'id': 'CUST-001', 'name': 'Priya', 'email': 'priya1@example.com', 'city': 'Rohtak', 'wallet': 2253}
{'id': 'CUST-002', 'name': 'Meera', 'email': 'meera2@example.com', 'city': 'Jaipur', 'wallet': 1143}
{'id': 'CUST-003', 'name': 'Priya', 'email': 'priya3@example.com', 'city': 'Lucknow', 'wallet': 712}
{'id': 'CUST-004', 'name': 'Farhan', 'email': 'farhan4@example.com', 'city': 'Rohtak', 'wallet': 244}
{'id': 'CUST-005', 'name': 'Priya', 'email': 'priya5@example.com', 'city': 'Jaipur', 'wallet': 1905}Run it twice: the output is identical. Remove the random.seed(42) line and the data changes on every run. Notice that each email includes the customer number, so the generated emails stay unique even when names repeat. The domain example.com is reserved for documentation and testing, so no real person receives mail there.
Masking personal data
If a team must use production-like data, data masking replaces sensitive values with safe ones while keeping the format, so the application still behaves normally. This example masks names, emails and phone numbers:
import re
def mask_email(email):
user, domain = email.split("@")
return user[0] + "***@" + domain
def mask_phone(phone):
digits = re.sub(r"\D", "", phone)
return "XXXXXX" + digits[-4:]
production_rows = [
{"name": "Ananya Sharma", "email": "ananya.sharma@mailbox.in", "phone": "+91 98765 43210"},
{"name": "Farhan Ali", "email": "farhan.ali@mailbox.in", "phone": "98111-22334"},
]
for i, row in enumerate(production_rows, start=1):
safe = {
"name": f"Customer {i}",
"email": mask_email(row["email"]),
"phone": mask_phone(row["phone"]),
}
print(safe){'name': 'Customer 1', 'email': 'a***@mailbox.in', 'phone': 'XXXXXX3210'}
{'name': 'Customer 2', 'email': 'f***@mailbox.in', 'phone': 'XXXXXX2334'}The rows here are invented for the example. Masking is a step in a controlled process, run by people allowed to see the original data, before anything is copied to a test system.
Tricky inputs find bugs fast
Experienced testers keep a personal list of inputs that often break software: empty strings, spaces only, apostrophes, hyphens, non-English scripts, very long text, leading zeros, negative numbers, dates such as 29 February. Run this name validator against such a list:
function isValidName(name) {
return /^[A-Za-z ]+$/.test(name);
}
const trickyNames = [
"Priya Nair", // normal
"", // empty
" ", // only spaces
"D'Souza", // apostrophe
"Mohan-Lal", // hyphen
"प्रिया", // Hindi script
"A".repeat(300), // very long
];
for (const name of trickyNames) {
const label = name.length > 20 ? `${name.slice(0, 5)}... (${name.length} chars)` : JSON.stringify(name);
console.log(`${isValidName(name) ? "accepted" : "REJECTED"} ${label}`);
}accepted "Priya Nair" REJECTED "" accepted " " REJECTED "D'Souza" REJECTED "Mohan-Lal" REJECTED "प्रिया" accepted AAAAA... (300 chars)
Seven inputs revealed four defects: real names with apostrophes, hyphens and Indian scripts are refused, while a name of only spaces and a 300-character name are accepted. None of these would show up if you only tested "Priya Nair".
Managing test data
- Keep data separate from test steps, in CSV or JSON files, so the same test can run with new data.
- Reset state before each test. Create the account, cart or coupon the test needs at the start, and clean up after. Tests that share data break each other.
- Make data unique where the system requires it (emails, phone numbers, order IDs), for example by adding a counter or timestamp.
- Version your data files with your test code, so everyone runs the same data.
- Document special accounts: "user
locked1@example.comis always locked" saves your team hours.
Common mistakes
- Testing with one perfect value. Real users type spaces, capital letters, apostrophes and very long text.
- Random data with no seed. A failure you cannot reproduce wastes everyone's time.
- Unmasked production data in test systems. It is a privacy and legal risk.
- Tests that depend on leftover data. If a test needs a record, the test should create it.
Key points
- Test data includes valid, invalid, boundary, empty, special-character, volume and state data.
- Synthetic data is safe and flexible; a fixed random seed makes it repeatable.
- Real personal data must be masked or avoided in test environments.
- A short list of tricky inputs (spaces, apostrophes, other scripts, very long text) finds many defects.
- Keep data separate from steps, reset state for every test, and version your data files.
Exercise
Rewrite isValidName() so that it accepts letters from any language, spaces, apostrophes and hyphens, rejects empty or spaces-only names, and rejects names longer than 100 characters. Hint: the regular expression /^\p{L}[\p{L}\p{M} '-]*$/u matches a letter followed by letters, vowel signs, spaces, apostrophes or hyphens in any script. Run it on the same tricky list.
Show solution
function isValidName(name) {
const trimmed = name.trim();
if (trimmed.length === 0 || trimmed.length > 100) return false;
return /^\p{L}[\p{L}\p{M} '-]*$/u.test(trimmed);
}
const trickyNames = ["Priya Nair", "", " ", "D'Souza", "Mohan-Lal", "प्रिया", "A".repeat(300)];
for (const name of trickyNames) {
console.log(isValidName(name) ? "accepted" : "REJECTED", JSON.stringify(name.slice(0, 12)));
}accepted "Priya Nair" REJECTED "" REJECTED " " accepted "D'Souza" accepted "Mohan-Lal" accepted "प्रिया" REJECTED "AAAAAAAAAAAA"
The u flag turns on Unicode mode and \p{L} means "any letter in any script". Indian scripts also use vowel signs (matras) such as the ि in प्रिया, which Unicode counts as marks, not letters, so \p{M} is needed too. Without it the Hindi name is still refused, which is exactly the kind of bug tricky test data exposes. Trimming first stops a spaces-only name from passing.
Quick Quiz
1. Why do testers set a random seed when generating test data?
The same seed produces the same sequence, so a failing run can be repeated exactly.
2. What should happen to real customer data before it is used in a test environment?
Personal data in less-protected test systems is a privacy and legal risk.
3. A form accepts a name made only of spaces. What kind of test data found this?
Spaces-only input is a classic empty-data case that validators often miss.
4. Why add a counter to generated email addresses?
Many systems reject duplicate emails, so generated data must stay unique.
Learn Software Testing with a mentor
Free lessons get you started. Our Software Testing training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
