Python 2 and 3 compatibility utilities
—
Utilities for writing tests that work across Python versions, including assertion method compatibility. These functions provide unified interfaces for test assertions that handle the method name differences between Python 2 and 3 unittest modules.
Functions that provide consistent assertion methods across Python versions by wrapping the appropriate unittest methods.
def assertCountEqual(self, *args, **kwargs) -> None
"""Assert that two iterables have the same elements regardless of order."""
def assertRaisesRegex(self, *args, **kwargs) -> ContextManager
"""Assert that an exception is raised and its message matches a regex pattern."""
def assertRegex(self, *args, **kwargs) -> None
"""Assert that a string matches a regular expression pattern."""
def assertNotRegex(self, *args, **kwargs) -> None
"""Assert that a string does not match a regular expression pattern."""Note: These functions are wrappers that delegate to the appropriate unittest method based on Python version, using the correct method names (assertItemsEqual/assertCountEqual, assertRaisesRegexp/assertRaisesRegex, etc.). They accept the same arguments as the underlying unittest methods.
Usage Examples:
import six
import unittest
import re
class MyTestCase(unittest.TestCase):
def test_count_equal_assertion(self):
"""Test assertCountEqual functionality."""
list1 = [1, 2, 3, 2]
list2 = [2, 1, 3, 2]
# Use six.assertCountEqual for cross-version compatibility
six.assertCountEqual(self, list1, list2)
# This assertion would fail
with self.assertRaises(AssertionError):
six.assertCountEqual(self, [1, 2, 3], [1, 2, 3, 4])
def test_regex_assertions(self):
"""Test regex-based assertions."""
text = "Hello, World! This is a test message."
# Assert text matches pattern
six.assertRegex(self, text, r"Hello.*World")
six.assertRegex(self, text, re.compile(r"\w+\s+\w+"))
# Assert text does not match pattern
six.assertNotRegex(self, text, r"goodbye")
six.assertNotRegex(self, text, re.compile(r"\d+"))
def test_exception_with_regex(self):
"""Test exception raising with regex matching."""
def problematic_function():
raise ValueError("Invalid input: expected number, got string")
# Assert exception is raised with matching message
with six.assertRaisesRegex(self, ValueError, r"Invalid input.*expected"):
problematic_function()
# Using compiled regex
pattern = re.compile(r"expected \w+, got \w+")
with six.assertRaisesRegex(self, ValueError, pattern):
problematic_function()
if __name__ == '__main__':
unittest.main()String constants that provide the correct method names for different Python versions.
_assertCountEqual: str # Method name for count equal assertion
_assertRaisesRegex: str # Method name for raises regex assertion
_assertRegex: str # Method name for regex assertion
_assertNotRegex: str # Method name for not regex assertionThese constants contain the appropriate method names:
assertItemsEqual, assertRaisesRegexp, assertRegexpMatches, assertNotRegexpMatchesassertCountEqual, assertRaisesRegex, assertRegex, assertNotRegexUsage Example:
import six
import unittest
class MyTestCase(unittest.TestCase):
def test_using_constants(self):
"""Example of using method name constants directly."""
# Get the appropriate method for the current Python version
count_equal_method = getattr(self, six._assertCountEqual)
# Use the method
count_equal_method([1, 2, 3], [3, 2, 1])
# Similarly for regex assertions
regex_method = getattr(self, six._assertRegex)
regex_method("hello world", r"hello.*world")import six
import unittest
class CrossVersionTestCase(unittest.TestCase):
"""Base test case with cross-version assertion helpers."""
def assertCountEqual(self, first, second, msg=None):
"""Wrapper for assertCountEqual that works across versions."""
return six.assertCountEqual(self, first, second, msg)
def assertRaisesRegex(self, expected_exception, expected_regex):
"""Wrapper for assertRaisesRegex that works across versions."""
return six.assertRaisesRegex(self, expected_exception, expected_regex)
def assertRegex(self, text, expected_regex, msg=None):
"""Wrapper for assertRegex that works across versions."""
return six.assertRegex(self, text, expected_regex, msg)
def assertNotRegex(self, text, unexpected_regex, msg=None):
"""Wrapper for assertNotRegex that works across versions."""
return six.assertNotRegex(self, text, unexpected_regex, msg)
def assertStringTypes(self, value, msg=None):
"""Assert that value is a string type (works across versions)."""
self.assertIsInstance(value, six.string_types, msg)
def assertTextType(self, value, msg=None):
"""Assert that value is a text type (unicode/str)."""
self.assertIsInstance(value, six.text_type, msg)
def assertBinaryType(self, value, msg=None):
"""Assert that value is a binary type (bytes/str)."""
self.assertIsInstance(value, six.binary_type, msg)
# Usage example
class StringProcessorTest(CrossVersionTestCase):
def test_string_processing(self):
"""Test string processing functionality."""
from mymodule import process_string
result = process_string("hello")
# Use cross-version assertions
self.assertStringTypes(result)
self.assertRegex(result, r"^processed:")
# Test with unicode
unicode_input = six.u("héllo")
unicode_result = process_string(unicode_input)
self.assertTextType(unicode_result)import six
import unittest
class VersionAwareTest(unittest.TestCase):
"""Test class that adapts behavior based on Python version."""
def setUp(self):
"""Set up test fixtures based on Python version."""
if six.PY2:
self.string_type = unicode
self.binary_type = str
else:
self.string_type = str
self.binary_type = bytes
def test_string_handling(self):
"""Test that varies behavior by Python version."""
test_data = [
("hello", "HELLO"),
(six.u("wörld"), six.u("WÖRLD")),
]
for input_val, expected in test_data:
with self.subTest(input=input_val):
result = input_val.upper()
self.assertEqual(result, expected)
self.assertIsInstance(result, self.string_type)
@unittest.skipUnless(six.PY3, "Python 3 only test")
def test_python3_feature(self):
"""Test that only runs on Python 3."""
# Test Python 3 specific functionality
self.assertRegex("test string", r"test.*string")
@unittest.skipUnless(six.PY2, "Python 2 only test")
def test_python2_feature(self):
"""Test that only runs on Python 2."""
# Test Python 2 specific functionality
self.assertRegexpMatches("test string", r"test.*string")
# Decorator for version-specific tests
def py2_only(func):
"""Decorator to skip test on Python 3."""
return unittest.skipUnless(six.PY2, "Python 2 only")(func)
def py3_only(func):
"""Decorator to skip test on Python 2."""
return unittest.skipUnless(six.PY3, "Python 3 only")(func)
class MyFeatureTest(unittest.TestCase):
@py2_only
def test_legacy_behavior(self):
"""Test legacy behavior specific to Python 2."""
pass
@py3_only
def test_modern_behavior(self):
"""Test modern behavior specific to Python 3."""
passimport six
import unittest
try:
from unittest.mock import Mock, patch, MagicMock
except ImportError:
# Python 2 fallback
from mock import Mock, patch, MagicMock
class MockingTest(unittest.TestCase):
"""Example of version-aware mocking."""
def test_mocked_function(self):
"""Test with mocked function using six utilities."""
with patch('mymodule.some_function') as mock_func:
mock_func.return_value = six.u("mocked result")
from mymodule import call_some_function
result = call_some_function()
# Verify the result using six assertions
six.assertRegex(self, result, r"mocked.*result")
mock_func.assert_called_once()
def test_string_type_handling(self):
"""Test mocking with proper string type handling."""
mock_obj = Mock()
mock_obj.get_data.return_value = six.ensure_text("test data")
result = mock_obj.get_data()
# Verify result is proper text type
self.assertIsInstance(result, six.text_type)
six.assertRegex(self, result, r"test.*data")
# Test runner helper
def run_cross_version_tests():
"""Helper to run tests with version information."""
import sys
six.print_(f"Running tests on Python {sys.version}")
six.print_(f"Six version: {six.__version__}")
six.print_(f"PY2: {six.PY2}, PY3: {six.PY3}")
# Discover and run tests
loader = unittest.TestLoader()
suite = loader.discover('.')
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result.wasSuccessful()
if __name__ == '__main__':
success = run_cross_version_tests()
sys.exit(0 if success else 1)import six
import pytest
class TestSixIntegration:
"""pytest tests using six utilities."""
def test_assertion_methods(self):
"""Test six assertion methods with pytest."""
# Note: six assertion methods expect unittest.TestCase self
# Create a minimal test case instance
import unittest
test_case = unittest.TestCase()
# Use six assertions
six.assertCountEqual(test_case, [1, 2, 3], [3, 2, 1])
six.assertRegex(test_case, "hello world", r"hello.*world")
with pytest.raises(AssertionError):
six.assertCountEqual(test_case, [1, 2], [1, 2, 3])
@pytest.mark.skipif(not six.PY3, reason="Python 3 only")
def test_py3_specific(self):
"""Test that only runs on Python 3."""
assert six.PY3
assert not six.PY2
def test_string_types(self):
"""Test string type checking with pytest."""
test_string = "hello"
assert isinstance(test_string, six.string_types)
if six.PY2:
unicode_string = six.u("hello")
assert isinstance(unicode_string, six.text_type)This comprehensive testing utilities documentation provides developers with all the tools they need to write cross-version compatible tests using the six library.
Install with Tessl CLI
npx tessl i tessl/pypi-six