CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-testtools

Extensions to the Python standard library unit testing framework

Overview
Eval results
Files

helpers.mddocs/

Helper Utilities

Utility functions for common testing tasks including safe imports, dictionary manipulation, monkey patching, and test organization helpers.

Capabilities

Safe Import Utilities

Functions for safely importing modules with fallback handling.

def try_import(name, alternative=None, error_callback=None):
    """
    Attempt to import a module, with a fallback.
    
    Safely attempts to import a module or attribute, returning
    an alternative value if the import fails. Useful for optional
    dependencies and cross-version compatibility.
    
    Args:
        name (str): The name of the object to import (e.g., 'os.path.join')
        alternative: The value to return if import fails (default: None)
        error_callback (callable): Function called with ImportError if import fails
        
    Returns:
        The imported object or the alternative value
        
    Example:
        # Try to import optional dependency
        numpy = try_import('numpy', alternative=None)
        if numpy is not None:
            # Use numpy functionality
            pass
            
        # Import specific function with fallback
        json_loads = try_import('orjson.loads', json.loads)
    """

Dictionary Manipulation

Utility functions for common dictionary operations in testing contexts.

def map_values(function, dictionary):
    """
    Map function across the values of a dictionary.
    
    Applies a function to each value in a dictionary,
    preserving the key-value structure.
    
    Args:
        function (callable): Function to apply to each value
        dictionary (dict): Dictionary to transform
        
    Returns:
        dict: New dictionary with transformed values
        
    Example:
        data = {'a': 1, 'b': 2, 'c': 3}
        doubled = map_values(lambda x: x * 2, data)
        # {'a': 2, 'b': 4, 'c': 6}
    """

def filter_values(function, dictionary):
    """
    Filter dictionary by its values using a predicate function.
    
    Returns a new dictionary containing only the key-value pairs
    where the value satisfies the predicate function.
    
    Args:
        function (callable): Predicate function returning bool
        dictionary (dict): Dictionary to filter
        
    Returns:
        dict: Filtered dictionary
        
    Example:
        data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
        evens = filter_values(lambda x: x % 2 == 0, data)
        # {'b': 2, 'd': 4}
    """

def dict_subtract(a, b):
    """
    Return the part of dictionary a that's not in dictionary b.
    
    Creates a new dictionary containing key-value pairs from 'a'
    whose keys are not present in 'b'.
    
    Args:
        a (dict): Source dictionary
        b (dict): Dictionary whose keys to exclude
        
    Returns:
        dict: Dictionary with keys from a not in b
        
    Example:
        a = {'x': 1, 'y': 2, 'z': 3}
        b = {'y': 5, 'w': 6}
        result = dict_subtract(a, b)
        # {'x': 1, 'z': 3}
    """

def list_subtract(a, b):
    """
    Return a list with elements of a not in b.
    
    Creates a new list containing elements from 'a' that are
    not present in 'b'. If an element appears multiple times
    in 'a' and once in 'b', it will appear n-1 times in result.
    
    Args:
        a (list): Source list
        b (list): List of elements to remove
        
    Returns:
        list: List with elements from a not in b
        
    Example:
        a = [1, 2, 3, 2, 4]
        b = [2, 5]
        result = list_subtract(a, b)
        # [1, 3, 2, 4]  (one instance of 2 removed)
    """

Test Organization Utilities

Functions for working with test cases and test organization.

def clone_test_with_new_id(test, new_id):
    """
    Clone a test with a new test ID.
    
    Creates a copy of a test case with a different identifier,
    useful for parameterized testing or test case variations.
    
    Args:
        test: Original TestCase instance
        new_id (str): New test identifier
        
    Returns:
        TestCase: Cloned test with new ID
        
    Example:
        original_test = MyTest('test_function')
        variant_test = clone_test_with_new_id(original_test, 'test_function_variant')
    """

def iterate_tests(test_suite_or_case):
    """
    Iterate through all individual tests in a test suite.
    
    Recursively flattens test suites to yield individual
    test cases, useful for test discovery and analysis.
    
    Args:
        test_suite_or_case: TestSuite or TestCase to iterate
        
    Yields:
        TestCase: Individual test cases
        
    Example:
        suite = TestSuite()
        # ... add tests to suite ...
        for test in iterate_tests(suite):
            print(f"Found test: {test.id()}")
    """

def unique_text_generator():
    """
    Generate unique text strings for test isolation.
    
    Creates a generator that yields unique text strings,
    useful for generating unique identifiers in tests.
    
    Yields:
        str: Unique text strings
        
    Example:
        generator = unique_text_generator()
        unique1 = next(generator)  # "0"
        unique2 = next(generator)  # "1"
        unique3 = next(generator)  # "2"
    """

Monkey Patching

Classes and functions for runtime patching during tests.

class MonkeyPatcher:
    """
    Apply and remove multiple patches as a coordinated group.
    
    Manages multiple monkey patches with automatic cleanup,
    ensuring all patches are properly restored after use.
    """
    
    def __init__(self):
        """Create a new MonkeyPatcher instance."""
    
    def add_patch(self, obj, attribute, new_value):
        """
        Add a patch to be applied.
        
        Args:
            obj: Object to patch
            attribute (str): Attribute name to patch
            new_value: New value for the attribute
        """
    
    def patch(self):
        """
        Apply all registered patches.
        
        Applies all patches that have been registered with add_patch().
        Should be called before the code that needs the patches.
        """
    
    def restore(self):
        """
        Restore all patched attributes to their original values.
        
        Undoes all patches applied by patch(), restoring the
        original attribute values.
        """
    
    def __enter__(self):
        """
        Context manager entry - applies patches.
        
        Returns:
            MonkeyPatcher: Self for context manager protocol
        """
        self.patch()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Context manager exit - restores patches.
        
        Args:
            exc_type: Exception type (if any)
            exc_val: Exception value (if any) 
            exc_tb: Exception traceback (if any)
        """
        self.restore()

def patch(obj, attribute, new_value):
    """
    Apply a simple monkey patch.
    
    Convenience function for applying a single patch.
    For multiple patches, use MonkeyPatcher class.
    
    Args:
        obj: Object to patch
        attribute (str): Attribute name to patch  
        new_value: New value for the attribute
        
    Returns:
        The original value that was replaced
        
    Example:
        # Patch a function temporarily
        original = patch(os, 'getcwd', lambda: '/fake/path')
        try:
            # Code that uses os.getcwd()
            pass
        finally:
            # Restore original
            setattr(os, 'getcwd', original)
    """

Standalone Assertions

Assertion functions that can be used outside of TestCase.

def assert_that(matchee, matcher, message='', verbose=False):
    """
    Assert that matchee matches the given matcher.
    
    Standalone assertion function using testtools matchers,
    can be used outside of TestCase instances for utility
    functions and general-purpose assertions.
    
    Args:
        matchee: Object to be matched
        matcher: Matcher instance to apply
        message (str): Optional failure message
        verbose (bool): Include detailed mismatch information
        
    Raises:
        MismatchError: If matcher does not match matchee
        
    Example:
        from testtools.assertions import assert_that
        from testtools.matchers import Equals, GreaterThan
        
        # Use in utility functions
        def validate_config(config):
            assert_that(config['timeout'], GreaterThan(0))
            assert_that(config['host'], Contains('.'))
    """

Compatibility Utilities

Functions for cross-Python version compatibility.

def reraise(exc_type, exc_value, traceback):
    """
    Re-raise an exception with its original traceback.
    
    Properly re-raises exceptions preserving the original
    traceback information across Python versions.
    
    Args:
        exc_type: Exception type
        exc_value: Exception instance
        traceback: Exception traceback
    """

def text_repr(obj):
    """
    Get text representation with proper escaping.
    
    Returns a properly escaped text representation of an object,
    handling unicode and special characters correctly.
    
    Args:
        obj: Object to represent
        
    Returns:
        str: Escaped text representation
    """

def unicode_output_stream(stream):
    """
    Get unicode-capable output stream.
    
    Wraps a stream to ensure it can handle unicode output
    properly across different Python versions and platforms.
    
    Args:
        stream: Original output stream
        
    Returns:
        Stream: Unicode-capable stream wrapper
    """

Usage Examples

Safe Import Patterns

import testtools
from testtools.helpers import try_import

class MyTest(testtools.TestCase):
    
    def setUp(self):
        super().setUp()
        
        # Try to import optional dependencies
        self.numpy = try_import('numpy')
        self.pandas = try_import('pandas')
        self.requests = try_import('requests', error_callback=self._log_import_error)
    
    def _log_import_error(self, error):
        """Log import errors for debugging."""
        self.addDetail('import_error', 
                      testtools.content.text_content(str(error)))
    
    def test_with_optional_numpy(self):
        if self.numpy is None:
            self.skip("NumPy not available")
        
        # Use numpy functionality
        arr = self.numpy.array([1, 2, 3])
        self.assertEqual(len(arr), 3)
    
    def test_with_fallback_json(self):
        # Use fast JSON library if available, fall back to standard
        json_loads = try_import('orjson.loads', alternative=json.loads)
        json_dumps = try_import('orjson.dumps', alternative=json.dumps)
        
        data = {'test': True, 'value': 42}
        serialized = json_dumps(data)
        deserialized = json_loads(serialized)
        
        self.assertEqual(deserialized, data)

Dictionary Operations

import testtools
from testtools.helpers import map_values, filter_values, dict_subtract

class DataProcessingTest(testtools.TestCase):
    
    def test_data_transformation(self):
        # Original data
        raw_data = {
            'temperature': 20,
            'humidity': 65,
            'pressure': 1013,
            'wind_speed': 15
        }
        
        # Transform all values (Celsius to Fahrenheit)
        fahrenheit_data = map_values(lambda c: c * 9/5 + 32, 
                                   {'temperature': raw_data['temperature']})
        self.assertEqual(fahrenheit_data['temperature'], 68.0)
        
        # Filter for specific conditions
        high_values = filter_values(lambda x: x > 50, raw_data)
        self.assertIn('humidity', high_values)
        self.assertIn('pressure', high_values)
        self.assertNotIn('temperature', high_values)
    
    def test_configuration_merge(self):
        # Base configuration
        base_config = {
            'host': 'localhost',
            'port': 8080,
            'debug': False,
            'timeout': 30
        }
        
        # Environment-specific overrides
        production_overrides = {
            'host': 'prod.example.com',
            'debug': False,
            'ssl': True
        }
        
        # Get settings that are only in base config
        base_only = dict_subtract(base_config, production_overrides)
        self.assertEqual(base_only, {'port': 8080, 'timeout': 30})
        
        # Merge configurations
        final_config = {**base_config, **production_overrides}
        self.assertEqual(final_config['host'], 'prod.example.com')
        self.assertTrue(final_config['ssl'])

Monkey Patching in Tests

import testtools
from testtools.helpers import MonkeyPatcher
import os
import time

class MonkeyPatchTest(testtools.TestCase):
    
    def test_with_context_manager(self):
        # Use MonkeyPatcher as context manager
        with MonkeyPatcher() as patcher:
            patcher.add_patch(os, 'getcwd', lambda: '/fake/working/dir')
            patcher.add_patch(time, 'time', lambda: 1234567890.0)
            
            # Test code that uses patched functions
            current_dir = os.getcwd()
            current_time = time.time()
            
            self.assertEqual(current_dir, '/fake/working/dir')
            self.assertEqual(current_time, 1234567890.0)
        
        # Outside context, original functions are restored
        self.assertNotEqual(os.getcwd(), '/fake/working/dir')
    
    def test_manual_patching(self):
        patcher = MonkeyPatcher()
        
        # Mock file system operations
        patcher.add_patch(os.path, 'exists', lambda path: path == '/fake/file')
        patcher.add_patch(os.path, 'isfile', lambda path: path.endswith('.txt'))
        
        try:
            patcher.patch()
            
            # Test with mocked file system
            self.assertTrue(os.path.exists('/fake/file'))
            self.assertFalse(os.path.exists('/other/file'))
            self.assertTrue(os.path.isfile('document.txt'))
            self.assertFalse(os.path.isfile('directory'))
            
        finally:
            patcher.restore()
    
    def test_method_patching(self):
        # Patch methods on test objects
        class MockService:
            def get_data(self):
                return "original data"
        
        service = MockService()
        patcher = MonkeyPatcher()
        patcher.add_patch(service, 'get_data', lambda: "mocked data")
        
        with patcher:
            result = service.get_data()
            self.assertEqual(result, "mocked data")
        
        # Method restored after context
        result = service.get_data()
        self.assertEqual(result, "original data")

Test Organization and Discovery

import testtools
from testtools.helpers import iterate_tests, clone_test_with_new_id

class TestOrganizationExample(testtools.TestCase):
    
    def test_suite_analysis(self):
        # Create a test suite
        suite = testtools.TestSuite()
        suite.addTest(TestOrganizationExample('test_method_1'))
        suite.addTest(TestOrganizationExample('test_method_2'))
        
        # Analyze all tests in suite
        test_count = 0
        test_names = []
        
        for test in iterate_tests(suite):
            test_count += 1
            test_names.append(test.id())
        
        self.assertEqual(test_count, 2)
        self.assertIn('TestOrganizationExample.test_method_1', test_names)
    
    def test_method_1(self):
        self.assertTrue(True)
    
    def test_method_2(self):
        self.assertTrue(True)

def create_parameterized_tests():
    """Create multiple test variants from a base test."""
    base_test = TestOrganizationExample('test_method_1')
    
    # Create variants with different parameters
    variants = []
    for i, param in enumerate(['param1', 'param2', 'param3']):
        variant = clone_test_with_new_id(base_test, f'test_method_1_variant_{i}')
        variant.test_param = param  # Add parameter to test
        variants.append(variant)
    
    return variants

class TestWithParameters(testtools.TestCase):
    
    def test_parameter_injection(self):
        # Use parameter if available
        param = getattr(self, 'test_param', 'default')
        self.assertIsNotNone(param)
        
        # Test behavior varies by parameter
        if param == 'param1':
            self.assertEqual(param, 'param1')
        else:
            self.assertNotEqual(param, 'param1')

Standalone Assertions

from testtools.assertions import assert_that
from testtools.matchers import Equals, GreaterThan, Contains, MatchesDict

def validate_user_data(user_data):
    """Validate user data using testtools assertions."""
    # Use standalone assertions for validation
    assert_that(user_data, MatchesDict({
        'id': GreaterThan(0),
        'name': Contains('@'),  # Assuming email format
        'age': GreaterThan(0)
    }))

def process_configuration(config_file):
    """Process configuration with validation."""
    config = load_config(config_file)
    
    # Validate required fields
    assert_that(config.get('database_url'), Contains('://'))
    assert_that(config.get('port'), GreaterThan(1000))
    assert_that(config.get('debug'), Equals(False))
    
    return config

# Usage in non-test code
try:
    user = {'id': 123, 'name': 'user@example.com', 'age': 25}
    validate_user_data(user)
    print("User data is valid")
except MismatchError as e:
    print(f"Validation failed: {e}")

Utility Integration

import testtools
from testtools.helpers import *
from testtools.matchers import *

class IntegratedUtilityTest(testtools.TestCase):
    
    def setUp(self):
        super().setUp()
        
        # Set up test environment with utilities
        self.patcher = MonkeyPatcher()
        self.unique_id_gen = unique_text_generator()
        
        # Mock external dependencies
        self.requests = try_import('requests')
        if self.requests:
            self.patcher.add_patch(self.requests, 'get', self._mock_http_get)
            self.patcher.patch()
    
    def tearDown(self):
        if hasattr(self, 'patcher'):
            self.patcher.restore()
        super().tearDown()
    
    def _mock_http_get(self, url):
        """Mock HTTP GET responses."""
        class MockResponse:
            def __init__(self, url):
                self.url = url
                self.status_code = 200
                self.text = f"Response from {url}"
            
            def json(self):
                return {"url": self.url, "status": "ok"}
        
        return MockResponse(url)
    
    def test_integrated_workflow(self):
        # Generate unique test data
        unique_name = next(self.unique_id_gen)
        test_data = {
            'name': f'test_user_{unique_name}',
            'email': f'user_{unique_name}@example.com'
        }
        
        # Process data with filtering
        valid_data = filter_values(lambda v: '@' in v, test_data)
        self.assertIn('email', valid_data)
        
        # Transform data
        formatted_data = map_values(str.upper, 
                                  {'name': test_data['name']})
        
        # Make HTTP request (mocked)
        if self.requests:
            response = self.requests.get('http://api.example.com/users')
            self.assertEqual(response.status_code, 200)
            
            # Validate response with standalone assertion
            assert_that(response.json(), MatchesDict({
                'url': Contains('api.example.com'),
                'status': Equals('ok')
            }))

Install with Tessl CLI

npx tessl i tessl/pypi-testtools

docs

content.md

helpers.md

index.md

matchers.md

test-cases.md

test-execution.md

test-results.md

twisted-support.md

tile.json