Data-Driven Testing: Complete Guide with Python
Data-driven testing (DDT) means writing a test once and running it against many sets of input data and expected results stored outside the test logic: in a parameter list, a CSV or JSON file, a spreadsheet or a database. In Python, the simplest way to do it is pytest's @pytest.mark.parametrize decorator. This guide explains when DDT is worth it, how to implement it with pytest and each data source, and how to manage test data without making the suite fragile.
pytest.mark.parametrize: one decorator turns each data row into a separate test, reported and re-runnable on its own- Choose the source by who maintains the data: inline lists for developers, CSV or JSON in Git for shared data, Excel for business teams, a database for live state
- Test isolation is mandatory: each data row must run independently, never relying on data created by another test
- Design the rows with test techniques such as equivalence partitioning and boundary value analysis, rather than adding rows at random
What is data-driven testing?
Data-driven testing is a test design approach in which the test logic is written once and the inputs and expected outputs come from a separate data set. The same scenario, for example "apply a discount code and check the total", runs once per data row. Adding a case means adding a row, not writing new code.
The approach increases coverage without duplicating test code, and it lets people who do not write code, such as business analysts, review or extend the test cases directly in the data file.
When should you use data-driven testing?
Use it when the same behaviour must be checked against many input combinations: calculations, validation rules, pricing, permissions, form fields or API payloads. Avoid it when each case needs different steps: forcing different scenarios into one parameterised test produces conditional logic that is harder to read than separate tests.
How do you do data-driven testing with pytest?
Decorate the test function with @pytest.mark.parametrize, name the arguments and pass a list of tuples. pytest generates one test item per tuple, so each case passes or fails on its own and appears separately in the report.
For example, @pytest.mark.parametrize("price, discount, expected", [(100, 0, 100), (100, 10, 90), (100, 25, 75)]) above def test_discount(price, discount, expected) produces three tests. Useful options:
- Readable names: pass
ids=[...], or wrap a row inpytest.param(..., id="ten-percent"), so the report says which case failed - Known bugs:
pytest.param(..., marks=pytest.mark.xfail)keeps a failing row visible without breaking the build - Combinations: stacking two
parametrizedecorators runs every combination of both lists - Dynamic data: the
pytest_generate_testshook inconftest.pyparameterises tests from any source at collection time
Avoid the alternative of looping over the data inside a single test: the first failing row stops the loop, the remaining rows never run, and the report shows one failure instead of telling you which cases broke.
Which data source should you use: CSV, JSON, Excel or a database?
Pick the source according to who maintains the data and how structured it is. Inline lists are best while the data set is small; move to a file when it grows or when non-developers need to edit it.
| Source | Best for | Python tools | Watch out for |
|---|---|---|---|
| Inline list | Small sets maintained by developers | parametrize | Becomes unreadable beyond a screenful |
| CSV | Flat tables, large volumes, easy diffs in Git | csv.DictReader, pandas | Everything is read as text: convert types explicitly |
| JSON | Nested data such as API payloads | json.load | Keep one clear schema per file |
| Excel | Data maintained by business teams | openpyxl, pandas | Binary file: hard to review; lock the column layout |
| Database | Data that must reflect the environment's real state | sqlite3, psycopg2, SQLAlchemy | Tests depend on the state of a shared system |
How do you load a CSV or JSON file into pytest?
Read the file at collection time, convert each row into a tuple or pytest.param, and pass the resulting list to parametrize.
- Store the file next to the tests, for example in
tests/data/discounts.csv, and version it in Git with the code. - Write a small loader function that opens the file with
csv.DictReaderorjson.loadand converts each value to the right type. - Give each row an identifier column and use it as the test id.
- Call the loader in the decorator:
@pytest.mark.parametrize("case", load_cases("discounts.csv"), ids=lambda c: c["id"]). - Fail loudly if the file is empty or a column is missing, so a broken data file never results in zero tests passing silently.
Does data-driven testing work for API and Playwright tests?
Yes. For API tests, parameterise the payload and the expected status code, and run each row through requests or the HTTP client of your framework; our API testing tools comparison shows which client fits your stack. In Playwright Test (TypeScript), the documented approach is to loop over an array of data and declare one test() per item, each with a unique title. In pytest-playwright, parametrize works exactly as with any other pytest test.
How do you manage test data safely?
Keep every test independent, generate what you can and never copy personal data from production.
- Isolation: each row creates what it needs; use pytest fixtures with
yieldto set up and clean up - Synthetic data: libraries such as Faker generate realistic names, addresses and emails without exposing real customers
- Edge cases: include empty strings, null values, negative numbers, very long inputs and special characters
- Ownership: every data file has an owner and changes go through code review
When the data comes from pipelines rather than from test files, you also need checks on the data itself; see our guide to data quality testing and validation.
How many data rows do you need?
Fewer than you think: one row per equivalence class plus the values at each boundary. Equivalence partitioning groups inputs that the system should treat the same way; boundary value analysis tests the values at the edges of each group, where bugs cluster. Both techniques are part of the ISTQB Foundation Level syllabus. For a discount valid from 1 to 50 items, test 0, 1, 50 and 51 rather than twenty random quantities.
Is data-driven testing the same as keyword-driven testing?
No. Data-driven testing varies the data while the steps stay fixed; keyword-driven testing describes the steps themselves as reusable keywords, as in Robot Framework. Behaviour-driven development (BDD) tools such as Cucumber or pytest-bdd support both ideas: "Scenario Outline" with an "Examples" table is data-driven testing written in Gherkin.
Frequently asked questions
What is data-driven testing in simple terms?
It is running the same automated test several times with different input data and expected results, stored separately from the test code. Adding a test case means adding a row of data, not writing a new test.
How do you parameterise a test in pytest?
Use the @pytest.mark.parametrize decorator with the argument names and a list of value tuples. pytest generates one independent test per tuple and reports each one separately.
Should test data be stored in CSV or JSON?
Use CSV for flat, tabular data and large volumes, and JSON for nested structures such as API payloads. Both are text files that can be versioned and reviewed in Git alongside the tests.
Can I use production data for data-driven tests?
Avoid it. Production data often contains personal information and changes over time, which makes tests unpredictable. Generate synthetic data with a library such as Faker, or use anonymised extracts.
What is the difference between data-driven and keyword-driven testing?
Data-driven testing keeps the steps fixed and varies the data. Keyword-driven testing expresses the steps themselves as reusable keywords, as in Robot Framework. Many frameworks combine the two.
Learn data-driven testing with Python
At Automation DataCamp (ADC), the Data-Driven Testing with Python course (50 h, online) covers advanced Python for testing, CSV, Excel and JSON data management, database integration and framework patterns, and ends with a complete project.
View the DDT course View the 3-month programmeInstallation, first web and API tests, Page Object and configuration in TypeScript, then a 90-day plan. Free 25-page PDF, guide in French.
Download the QA Automation Guide 2026Related articles

10 Best Practices for Test Automation
Essential practices for creating robust and maintainable tests.
Read more : 10 Best Practices for Test Automation
API Testing: Tool Comparison
Comparison of the best tools for testing APIs.
Read more : API Testing — Tool Comparison