Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Data-Driven Tests with Python unittest and ddt

Tech 1

Overview of ddt

When automating API tests, a single endpoint typically requires validation against multiple input combinations covering positive scenarios, boundary conditions, and error cases. Manually writing individual test methods for each permutation creates redundant code. The ddt (Data-Driven Tests) library integrates with Python's built-in unittest framwork to externalize test data, enabling a single test method to execute across diverse datasets.

The library provides four primary decorators:

  • @ddt: Class-level decorator enabling data-driven capabilities
  • @data: Specifies the dataset for a test method
  • @unpack: Distributes complex data structures (tuples, lists, dictionaries) into separate arguments
  • @file_data: Loads test cases from external JSON or YAML files

Basic Parameterization Patterns

Single Value Iteration

Apply the @data decorator with individual values to run the same assertion logic against different scalar inputs:

import unittest
from ddt import ddt, data

@ddt
class ScalarTestSuite(unittest.TestCase):
    
    @data(100, 200, 300)
    def test_numeric_validation(self, input_value):
        self.assertIsInstance(input_value, int)
        print(f"Processing value: {input_value}")

if __name__ == '__main__':
    unittest.main()

Structured Data Unpacking

For multi-parameter scenarios, pass tuples and utilize @unpack to distribute values across method arguments:

import unittest
from ddt import ddt, data, unpack

@ddt
class TupleUnpackingTests(unittest.TestCase):

    @data((10, 20, 30), (40, 50, 60))
    @unpack
    def test_arithmetic_operations(self, x, y, expected_sum):
        actual = x + y
        self.assertEqual(actual, expected_sum)
        print(f"Verified: {x} + {y} = {expected_sum}")

if __name__ == '__main__':
    unittest.main()

Dictionary Parameter Mapping

When working with named parameters, dictionaries provide clarity. The @unpack decorator maps dictionary values to keyword arguments:

import unittest
from ddt import ddt, data, unpack

@ddt
class DictionaryMappingTests(unittest.TestCase):

    @data([
        {'username': 'admin', 'role': 'superuser'}, 
        {'username': 'guest', 'role': 'readonly'}
    ])
    @unpack
    def test_user_permissions(self, username, role):
        print(f"Validating access rights for {username} with {role} privileges")
        self.assertIn(role, ['superuser', 'readonly', 'editor'])

if __name__ == '__main__':
    unittest.main()

External Data Integration with Excel

Real-world test automation often requires managing large datasets in spreadsheets. The following implementation demonstrates reading Excel files using xlrd and parameterizing HTTP requests with requests and ddt:

import unittest
import requests
import ddt
import xlrd

def extract_excel_dataset(file_path):
    """Parse Excel workbook and return list of row values."""
    dataset = []
    workbook = xlrd.open_workbook(file_path)
    worksheet = workbook.sheet_by_index(0)
    
    for row_idx in range(1, worksheet.nrows):
        row_data = list(worksheet.row_values(row_idx, 0, worksheet.ncols))
        dataset.append(row_data)
    return dataset

# Load test data from external file
test_scenarios = extract_excel_dataset('./test_data.xlsx')

# Authentication token (typically retrieved dynamically in production)
auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.sample.token"

def fetch_product_catalog(category_id, status_filter):
    """Retrieve filtered product listings from API."""
    request_headers = {
        'Authorization': f'Bearer {auth_token}',
        'Content-Type': 'application/json',
        'X-Request-ID': 'automated-test-suite'
    }
    
    query_params = {
        "category": category_id,
        "status": int(status_filter)
    }
    
    api_response = requests.get(
        "https://api.example.com/v1/products/catalog",
        headers=request_headers,
        params=query_params,
        verify=True
    )
    return api_response

@ddt.ddt
class ProductCatalogAPITests(unittest.TestCase):
    
    @ddt.data(*test_scenarios)
    @ddt.unpack
    def test_catalog_filter_combinations(self, category_code, availability_status):
        """Validate catalog responses across multiple filter combinations."""
        response = fetch_product_catalog(category_code, availability_status)
        response_payload = response.json()
        
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response_payload.get('status'), 200)
        self.assertIn('products', response_payload)

if __name__ == '__main__':
    unittest.main(verbosity=2)

This approach separates test logic from test data, allowing non-technical stakeholders to modify Excel spreadsheets without changing Python code. The verbosity=2 flag provides detailed execution traces for debugging complex paramter combinations.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.