Parameterized testing with any Python test framework
npx @tessl/cli install tessl/pypi-parameterized@0.9.0Parameterized testing with any Python test framework. This library provides comprehensive parameterized testing capabilities that eliminate repetitive test code by allowing developers to define test logic once and execute it across multiple parameter sets. It maintains compatibility with multiple test frameworks including nose, pytest, unittest, and unittest2.
pip install parameterizedfrom parameterized import parameterized, param, parameterized_classVersion information:
__version__: str # Package version ("0.9.0")from parameterized import parameterized, param, parameterized_class
import unittest
import math
# Basic parameterized test function
@parameterized([
(2, 2, 4),
(2, 3, 8),
(1, 9, 1),
(0, 9, 0),
])
def test_pow(base, exponent, expected):
assert math.pow(base, exponent) == expected
# Parameterized test in unittest class
class TestMathUnitTest(unittest.TestCase):
@parameterized.expand([
("negative", -1.5, -2.0),
("integer", 1, 1.0),
("large fraction", 1.6, 1),
])
def test_floor(self, name, input, expected):
self.assertEqual(math.floor(input), expected)
# Parameterized test class
@parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [
(1, 2, 3, 2),
(3, 4, 7, 12),
])
class TestArithmetic(unittest.TestCase):
def test_sum(self):
self.assertEqual(self.a + self.b, self.expected_sum)
def test_product(self):
self.assertEqual(self.a * self.b, self.expected_product)The parameterized library uses a decorator-based architecture that transforms test functions and classes at import time:
This design enables seamless integration with existing test suites while providing consistent parameterization across different test frameworks.
The core @parameterized decorator for creating parameterized test functions that work with nose, pytest, and standalone execution.
class parameterized:
def __init__(self, input, doc_func=None, skip_on_empty=False):
"""
Initialize parameterized decorator.
Parameters:
- input: Iterable of parameter sets or callable returning iterable
- doc_func: Function to generate test documentation (func, num, param) -> str
- skip_on_empty: If True, skip test when input is empty; if False, raise ValueError
"""
@classmethod
def to_safe_name(cls, s):
"""
Convert string to safe name for test case naming.
Parameters:
- s: String to convert
Returns:
String with non-alphanumeric characters replaced by underscores
"""The @parameterized.expand class method for parameterizing test methods within unittest.TestCase subclasses.
@classmethod
def expand(cls, input, name_func=None, doc_func=None, skip_on_empty=False, namespace=None, **legacy):
"""
Parameterize test methods in unittest.TestCase subclasses.
Parameters:
- input: Iterable of parameter sets or callable returning iterable
- name_func: Function to generate test method names (func, num, param) -> str
- doc_func: Function to generate test documentation (func, num, param) -> str
- skip_on_empty: If True, skip test when input is empty; if False, raise ValueError
- namespace: Namespace to inject test methods (defaults to caller's locals)
Returns:
Decorator function for test methods
"""The param class for specifying individual parameter sets with both positional and keyword arguments.
class param:
def __new__(cls, *args, **kwargs):
"""
Create parameter set with positional and keyword arguments.
Parameters:
- *args: Positional arguments for test function
- **kwargs: Keyword arguments for test function
Returns:
param instance containing args and kwargs
"""
@classmethod
def explicit(cls, args=None, kwargs=None):
"""
Create param by explicitly specifying args and kwargs lists.
Parameters:
- args: Tuple/list of positional arguments
- kwargs: Dictionary of keyword arguments
Returns:
param instance
"""
@classmethod
def from_decorator(cls, args):
"""
Create param from decorator arguments with automatic type handling.
Parameters:
- args: Single value, tuple, or param instance
Returns:
param instance
"""Function for creating multiple parameterized test classes with different attribute values.
def parameterized_class(attrs, input_values=None, class_name_func=None, classname_func=None):
"""
Parameterize test classes by setting attributes.
Parameters:
- attrs: String/list of attribute names or list of dicts with attribute values
- input_values: List of tuples with values for each attrs (when attrs is string/list)
- class_name_func: Function to generate class names (cls, idx, params_dict) -> str
- classname_func: Deprecated, use class_name_func instead
Returns:
Decorator function for test classes
"""Helper functions for parameter processing, test naming, and framework compatibility.
def parameterized_argument_value_pairs(func, p):
"""
Return tuples of parameterized arguments and their values.
Parameters:
- func: Test function to analyze
- p: param instance with arguments
Returns:
List of (arg_name, value) tuples
"""
def short_repr(x, n=64):
"""
Create shortened string representation guaranteed to be unicode.
Parameters:
- x: Object to represent
- n: Maximum length (default: 64)
Returns:
Unicode string representation
"""
def default_doc_func(func, num, p):
"""
Default function for generating test documentation.
Parameters:
- func: Test function
- num: Parameter set number
- p: param instance
Returns:
Documentation string or None
"""
def default_name_func(func, num, p):
"""
Default function for generating test names.
Parameters:
- func: Test function
- num: Parameter set number
- p: param instance
Returns:
Test name string
"""
def set_test_runner(name):
"""
Override automatic test runner detection.
Parameters:
- name: Test runner name ("unittest", "unittest2", "nose", "nose2", "pytest")
Raises:
TypeError: If runner name is invalid
"""
def detect_runner():
"""
Detect current test runner by examining the call stack.
Returns:
String name of detected test runner or None
"""from parameterized import parameterized, param
@parameterized([
param(1, 2, expected=3),
param("hello", " world", expected="hello world"),
param([1, 2], [3, 4], expected=[1, 2, 3, 4]),
])
def test_operations(a, b, expected):
if isinstance(a, str):
result = a + b
elif isinstance(a, list):
result = a + b
else:
result = a + b
assert result == expectedfrom parameterized import parameterized
def custom_name_func(func, num, p):
return f"{func.__name__}_case_{num}_{p.args[0]}"
def custom_doc_func(func, num, p):
return f"Test case {num}: {func.__name__} with input {p.args[0]}"
@parameterized([
("positive",), ("negative",), ("zero",)
], name_func=custom_name_func, doc_func=custom_doc_func)
def test_number_type(number_type):
assert number_type in ["positive", "negative", "zero"]from unittest.mock import patch
from parameterized import parameterized
class TestWithMocks(unittest.TestCase):
@parameterized.expand([
("user1", "data1"),
("user2", "data2"),
])
@patch('my_module.external_service')
def test_service_calls(self, username, expected_data, mock_service):
mock_service.get_data.return_value = expected_data
result = my_module.process_user(username)
mock_service.get_data.assert_called_once_with(username)
self.assertEqual(result, expected_data)from parameterized import parameterized_class
@parameterized_class([
{"database": "sqlite", "connection_string": ":memory:", "supports_transactions": True},
{"database": "mysql", "connection_string": "mysql://localhost/test", "supports_transactions": True},
{"database": "redis", "connection_string": "redis://localhost", "supports_transactions": False},
])
class TestDatabaseOperations(unittest.TestCase):
def setUp(self):
self.db = create_database_connection(self.database, self.connection_string)
def test_basic_operations(self):
self.db.insert("key", "value")
result = self.db.get("key")
self.assertEqual(result, "value")
def test_transaction_support(self):
if self.supports_transactions:
with self.db.transaction():
self.db.insert("key", "value")
else:
with self.assertRaises(NotImplementedError):
self.db.transaction()