gh-71339: Add additional assertion methods for unittest (GH-128707)

Add the following methods:

* assertHasAttr() and assertNotHasAttr()
* assertIsSubclass() and assertNotIsSubclass()
* assertStartsWith() and assertNotStartsWith()
* assertEndsWith() and assertNotEndsWith()

Also improve error messages for assertIsInstance() and
assertNotIsInstance().
This commit is contained in:
Serhiy Storchaka 2025-01-14 10:02:38 +02:00 committed by GitHub
parent 41f73501ec
commit 06cad77a5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 555 additions and 100 deletions

View file

@ -883,6 +883,12 @@ Test cases
| :meth:`assertNotIsInstance(a, b) | ``not isinstance(a, b)`` | 3.2 |
| <TestCase.assertNotIsInstance>` | | |
+-----------------------------------------+-----------------------------+---------------+
| :meth:`assertIsSubclass(a, b) | ``issubclass(a, b)`` | 3.14 |
| <TestCase.assertIsSubclass>` | | |
+-----------------------------------------+-----------------------------+---------------+
| :meth:`assertNotIsSubclass(a, b) | ``not issubclass(a, b)`` | 3.14 |
| <TestCase.assertNotIsSubclass>` | | |
+-----------------------------------------+-----------------------------+---------------+
All the assert methods accept a *msg* argument that, if specified, is used
as the error message on failure (see also :data:`longMessage`).
@ -961,6 +967,15 @@ Test cases
.. versionadded:: 3.2
.. method:: assertIsSubclass(cls, superclass, msg=None)
assertNotIsSubclass(cls, superclass, msg=None)
Test that *cls* is (or is not) a subclass of *superclass* (which can be a
class or a tuple of classes, as supported by :func:`issubclass`).
To check for the exact type, use :func:`assertIs(cls, superclass) <assertIs>`.
.. versionadded:: next
It is also possible to check the production of exceptions, warnings, and
log messages using the following methods:
@ -1210,6 +1225,24 @@ Test cases
| <TestCase.assertCountEqual>` | elements in the same number, | |
| | regardless of their order. | |
+---------------------------------------+--------------------------------+--------------+
| :meth:`assertStartsWith(a, b) | ``a.startswith(b)`` | 3.14 |
| <TestCase.assertStartsWith>` | | |
+---------------------------------------+--------------------------------+--------------+
| :meth:`assertNotStartsWith(a, b) | ``not a.startswith(b)`` | 3.14 |
| <TestCase.assertNotStartsWith>` | | |
+---------------------------------------+--------------------------------+--------------+
| :meth:`assertEndsWith(a, b) | ``a.endswith(b)`` | 3.14 |
| <TestCase.assertEndsWith>` | | |
+---------------------------------------+--------------------------------+--------------+
| :meth:`assertNotEndsWith(a, b) | ``not a.endswith(b)`` | 3.14 |
| <TestCase.assertNotEndsWith>` | | |
+---------------------------------------+--------------------------------+--------------+
| :meth:`assertHasAttr(a, b) | ``hastattr(a, b)`` | 3.14 |
| <TestCase.assertHasAttr>` | | |
+---------------------------------------+--------------------------------+--------------+
| :meth:`assertNotHasAttr(a, b) | ``not hastattr(a, b)`` | 3.14 |
| <TestCase.assertNotHasAttr>` | | |
+---------------------------------------+--------------------------------+--------------+
.. method:: assertAlmostEqual(first, second, places=7, msg=None, delta=None)
@ -1279,6 +1312,34 @@ Test cases
.. versionadded:: 3.2
.. method:: assertStartsWith(s, prefix, msg=None)
.. method:: assertNotStartsWith(s, prefix, msg=None)
Test that the Unicode or byte string *s* starts (or does not start)
with a *prefix*.
*prefix* can also be a tuple of strings to try.
.. versionadded:: next
.. method:: assertEndsWith(s, suffix, msg=None)
.. method:: assertNotEndsWith(s, suffix, msg=None)
Test that the Unicode or byte string *s* ends (or does not end)
with a *suffix*.
*suffix* can also be a tuple of strings to try.
.. versionadded:: next
.. method:: assertHasAttr(obj, name, msg=None)
.. method:: assertNotHasAttr(obj, name, msg=None)
Test that the object *obj* has (or has not) an attribute *name*.
.. versionadded:: next
.. _type-specific-methods:
The :meth:`assertEqual` method dispatches the equality check for objects of

View file

@ -670,6 +670,23 @@ unittest
directory again. It was removed in Python 3.11.
(Contributed by Jacob Walls in :gh:`80958`.)
* A number of new methods were added in the :class:`~unittest.TestCase` class
that provide more specialized tests.
- :meth:`~unittest.TestCase.assertHasAttr` and
:meth:`~unittest.TestCase.assertNotHasAttr` check whether the object
has a particular attribute.
- :meth:`~unittest.TestCase.assertIsSubclass` and
:meth:`~unittest.TestCase.assertNotIsSubclass` check whether the object
is a subclass of a particular class, or of one of a tuple of classes.
- :meth:`~unittest.TestCase.assertStartsWith`,
:meth:`~unittest.TestCase.assertNotStartsWith`,
:meth:`~unittest.TestCase.assertEndsWith` and
:meth:`~unittest.TestCase.assertNotEndsWith` check whether the Unicode
or byte string starts or ends with particular string(s).
(Contributed by Serhiy Storchaka in :gh:`71339`.)
urllib
------

View file

@ -405,14 +405,6 @@ class OperatorsTest(unittest.TestCase):
class ClassPropertiesAndMethods(unittest.TestCase):
def assertHasAttr(self, obj, name):
self.assertTrue(hasattr(obj, name),
'%r has no attribute %r' % (obj, name))
def assertNotHasAttr(self, obj, name):
self.assertFalse(hasattr(obj, name),
'%r has unexpected attribute %r' % (obj, name))
def test_python_dicts(self):
# Testing Python subclass of dict...
self.assertTrue(issubclass(dict, dict))

View file

@ -280,11 +280,6 @@ class DebuggerTests(unittest.TestCase):
return out
def assertEndsWith(self, actual, exp_end):
'''Ensure that the given "actual" string ends with "exp_end"'''
self.assertTrue(actual.endswith(exp_end),
msg='%r did not end with %r' % (actual, exp_end))
def assertMultilineMatches(self, actual, pattern):
m = re.match(pattern, actual, re.DOTALL)
if not m:

View file

@ -43,12 +43,6 @@ class FunctionalAPIBase(util.DiskSetup):
with self.subTest(path_parts=path_parts):
yield path_parts
def assertEndsWith(self, string, suffix):
"""Assert that `string` ends with `suffix`.
Used to ignore an architecture-specific UTF-16 byte-order mark."""
self.assertEqual(string[-len(suffix) :], suffix)
def test_read_text(self):
self.assertEqual(
resources.read_text(self.anchor01, 'utf-8.file'),

View file

@ -31,14 +31,6 @@ class PyclbrTest(TestCase):
print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr)
self.fail("%r missing" % missing.pop())
def assertHasattr(self, obj, attr, ignore):
''' succeed iff hasattr(obj,attr) or attr in ignore. '''
if attr in ignore: return
if not hasattr(obj, attr): print("???", attr)
self.assertTrue(hasattr(obj, attr),
'expected hasattr(%r, %r)' % (obj, attr))
def assertHaskey(self, obj, key, ignore):
''' succeed iff key in obj or key in ignore. '''
if key in ignore: return
@ -86,7 +78,7 @@ class PyclbrTest(TestCase):
for name, value in dict.items():
if name in ignore:
continue
self.assertHasattr(module, name, ignore)
self.assertHasAttr(module, name, ignore)
py_item = getattr(module, name)
if isinstance(value, pyclbr.Function):
self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))

View file

@ -59,20 +59,6 @@ CANNOT_SUBCLASS_INSTANCE = 'Cannot subclass an instance of %s'
class BaseTestCase(TestCase):
def assertIsSubclass(self, cls, class_or_tuple, msg=None):
if not issubclass(cls, class_or_tuple):
message = '%r is not a subclass of %r' % (cls, class_or_tuple)
if msg is not None:
message += ' : %s' % msg
raise self.failureException(message)
def assertNotIsSubclass(self, cls, class_or_tuple, msg=None):
if issubclass(cls, class_or_tuple):
message = '%r is a subclass of %r' % (cls, class_or_tuple)
if msg is not None:
message += ' : %s' % msg
raise self.failureException(message)
def clear_caches(self):
for f in typing._cleanups:
f()
@ -1252,10 +1238,6 @@ class UnpackTests(BaseTestCase):
class TypeVarTupleTests(BaseTestCase):
def assertEndsWith(self, string, tail):
if not string.endswith(tail):
self.fail(f"String {string!r} does not end with {tail!r}")
def test_name(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Ts.__name__, 'Ts')

View file

@ -10,6 +10,7 @@ import weakref
import inspect
import types
from collections import UserString
from copy import deepcopy
from test import support
@ -54,6 +55,10 @@ class Test(object):
self.events.append('tearDown')
class List(list):
pass
class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
### Set up attributes used by inherited tests
@ -85,7 +90,7 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
def runTest(self): raise MyException()
def test(self): pass
self.assertEqual(Test().id()[-13:], '.Test.runTest')
self.assertEndsWith(Test().id(), '.Test.runTest')
# test that TestCase can be instantiated with no args
# primarily for use at the interactive interpreter
@ -106,7 +111,7 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
def runTest(self): raise MyException()
def test(self): pass
self.assertEqual(Test('test').id()[-10:], '.Test.test')
self.assertEndsWith(Test('test').id(), '.Test.test')
# "class TestCase([methodName])"
# ...
@ -700,16 +705,120 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
self.assertRaises(self.failureException, self.assertIsNot, thing, thing)
def testAssertIsInstance(self):
thing = []
thing = List()
self.assertIsInstance(thing, list)
self.assertRaises(self.failureException, self.assertIsInstance,
thing, dict)
self.assertIsInstance(thing, (int, list))
with self.assertRaises(self.failureException) as cm:
self.assertIsInstance(thing, int)
self.assertEqual(str(cm.exception),
"[] is not an instance of <class 'int'>")
with self.assertRaises(self.failureException) as cm:
self.assertIsInstance(thing, (int, float))
self.assertEqual(str(cm.exception),
"[] is not an instance of any of (<class 'int'>, <class 'float'>)")
with self.assertRaises(self.failureException) as cm:
self.assertIsInstance(thing, int, 'ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertIsInstance(thing, int, msg='ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertNotIsInstance(self):
thing = []
self.assertNotIsInstance(thing, dict)
self.assertRaises(self.failureException, self.assertNotIsInstance,
thing, list)
thing = List()
self.assertNotIsInstance(thing, int)
self.assertNotIsInstance(thing, (int, float))
with self.assertRaises(self.failureException) as cm:
self.assertNotIsInstance(thing, list)
self.assertEqual(str(cm.exception),
"[] is an instance of <class 'list'>")
with self.assertRaises(self.failureException) as cm:
self.assertNotIsInstance(thing, (int, list))
self.assertEqual(str(cm.exception),
"[] is an instance of <class 'list'>")
with self.assertRaises(self.failureException) as cm:
self.assertNotIsInstance(thing, list, 'ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertNotIsInstance(thing, list, msg='ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertIsSubclass(self):
self.assertIsSubclass(List, list)
self.assertIsSubclass(List, (int, list))
with self.assertRaises(self.failureException) as cm:
self.assertIsSubclass(List, int)
self.assertEqual(str(cm.exception),
f"{List!r} is not a subclass of <class 'int'>")
with self.assertRaises(self.failureException) as cm:
self.assertIsSubclass(List, (int, float))
self.assertEqual(str(cm.exception),
f"{List!r} is not a subclass of any of (<class 'int'>, <class 'float'>)")
with self.assertRaises(self.failureException) as cm:
self.assertIsSubclass(1, int)
self.assertEqual(str(cm.exception), "1 is not a class")
with self.assertRaises(self.failureException) as cm:
self.assertIsSubclass(List, int, 'ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertIsSubclass(List, int, msg='ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertNotIsSubclass(self):
self.assertNotIsSubclass(List, int)
self.assertNotIsSubclass(List, (int, float))
with self.assertRaises(self.failureException) as cm:
self.assertNotIsSubclass(List, list)
self.assertEqual(str(cm.exception),
f"{List!r} is a subclass of <class 'list'>")
with self.assertRaises(self.failureException) as cm:
self.assertNotIsSubclass(List, (int, list))
self.assertEqual(str(cm.exception),
f"{List!r} is a subclass of <class 'list'>")
with self.assertRaises(self.failureException) as cm:
self.assertNotIsSubclass(1, int)
self.assertEqual(str(cm.exception), "1 is not a class")
with self.assertRaises(self.failureException) as cm:
self.assertNotIsSubclass(List, list, 'ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertNotIsSubclass(List, list, msg='ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertHasAttr(self):
a = List()
a.x = 1
self.assertHasAttr(a, 'x')
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(a, 'y')
self.assertEqual(str(cm.exception),
"List instance has no attribute 'y'")
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(a, 'y', 'ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(a, 'y', msg='ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertNotHasAttr(self):
a = List()
a.x = 1
self.assertNotHasAttr(a, 'y')
with self.assertRaises(self.failureException) as cm:
self.assertNotHasAttr(a, 'x')
self.assertEqual(str(cm.exception),
"List instance has unexpected attribute 'x'")
with self.assertRaises(self.failureException) as cm:
self.assertNotHasAttr(a, 'x', 'ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertNotHasAttr(a, 'x', msg='ababahalamaha')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertIn(self):
animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'}
@ -1864,6 +1973,186 @@ test case
pass
self.assertIsNone(value)
def testAssertStartswith(self):
self.assertStartsWith('ababahalamaha', 'ababa')
self.assertStartsWith('ababahalamaha', ('x', 'ababa', 'y'))
self.assertStartsWith(UserString('ababahalamaha'), 'ababa')
self.assertStartsWith(UserString('ababahalamaha'), ('x', 'ababa', 'y'))
self.assertStartsWith(bytearray(b'ababahalamaha'), b'ababa')
self.assertStartsWith(bytearray(b'ababahalamaha'), (b'x', b'ababa', b'y'))
self.assertStartsWith(b'ababahalamaha', bytearray(b'ababa'))
self.assertStartsWith(b'ababahalamaha',
(bytearray(b'x'), bytearray(b'ababa'), bytearray(b'y')))
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith('ababahalamaha', 'amaha')
self.assertEqual(str(cm.exception),
"'ababahalamaha' doesn't start with 'amaha'")
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith('ababahalamaha', ('x', 'y'))
self.assertEqual(str(cm.exception),
"'ababahalamaha' doesn't start with any of ('x', 'y')")
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith(b'ababahalamaha', 'ababa')
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith(b'ababahalamaha', ('amaha', 'ababa'))
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith([], 'ababa')
self.assertEqual(str(cm.exception), 'Expected str, not list')
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith('ababahalamaha', b'ababa')
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith('ababahalamaha', (b'amaha', b'ababa'))
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(TypeError):
self.assertStartsWith('ababahalamaha', ord('a'))
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith('ababahalamaha', 'amaha', 'abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertStartsWith('ababahalamaha', 'amaha', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertNotStartswith(self):
self.assertNotStartsWith('ababahalamaha', 'amaha')
self.assertNotStartsWith('ababahalamaha', ('x', 'amaha', 'y'))
self.assertNotStartsWith(UserString('ababahalamaha'), 'amaha')
self.assertNotStartsWith(UserString('ababahalamaha'), ('x', 'amaha', 'y'))
self.assertNotStartsWith(bytearray(b'ababahalamaha'), b'amaha')
self.assertNotStartsWith(bytearray(b'ababahalamaha'), (b'x', b'amaha', b'y'))
self.assertNotStartsWith(b'ababahalamaha', bytearray(b'amaha'))
self.assertNotStartsWith(b'ababahalamaha',
(bytearray(b'x'), bytearray(b'amaha'), bytearray(b'y')))
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith('ababahalamaha', 'ababa')
self.assertEqual(str(cm.exception),
"'ababahalamaha' starts with 'ababa'")
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith('ababahalamaha', ('x', 'ababa', 'y'))
self.assertEqual(str(cm.exception),
"'ababahalamaha' starts with 'ababa'")
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith(b'ababahalamaha', 'ababa')
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith(b'ababahalamaha', ('amaha', 'ababa'))
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith([], 'ababa')
self.assertEqual(str(cm.exception), 'Expected str, not list')
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith('ababahalamaha', b'ababa')
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith('ababahalamaha', (b'amaha', b'ababa'))
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(TypeError):
self.assertNotStartsWith('ababahalamaha', ord('a'))
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith('ababahalamaha', 'ababa', 'abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertNotStartsWith('ababahalamaha', 'ababa', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertEndswith(self):
self.assertEndsWith('ababahalamaha', 'amaha')
self.assertEndsWith('ababahalamaha', ('x', 'amaha', 'y'))
self.assertEndsWith(UserString('ababahalamaha'), 'amaha')
self.assertEndsWith(UserString('ababahalamaha'), ('x', 'amaha', 'y'))
self.assertEndsWith(bytearray(b'ababahalamaha'), b'amaha')
self.assertEndsWith(bytearray(b'ababahalamaha'), (b'x', b'amaha', b'y'))
self.assertEndsWith(b'ababahalamaha', bytearray(b'amaha'))
self.assertEndsWith(b'ababahalamaha',
(bytearray(b'x'), bytearray(b'amaha'), bytearray(b'y')))
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith('ababahalamaha', 'ababa')
self.assertEqual(str(cm.exception),
"'ababahalamaha' doesn't end with 'ababa'")
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith('ababahalamaha', ('x', 'y'))
self.assertEqual(str(cm.exception),
"'ababahalamaha' doesn't end with any of ('x', 'y')")
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith(b'ababahalamaha', 'amaha')
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith(b'ababahalamaha', ('ababa', 'amaha'))
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith([], 'amaha')
self.assertEqual(str(cm.exception), 'Expected str, not list')
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith('ababahalamaha', b'amaha')
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith('ababahalamaha', (b'ababa', b'amaha'))
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(TypeError):
self.assertEndsWith('ababahalamaha', ord('a'))
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith('ababahalamaha', 'ababa', 'abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertEndsWith('ababahalamaha', 'ababa', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
def testAssertNotEndswith(self):
self.assertNotEndsWith('ababahalamaha', 'ababa')
self.assertNotEndsWith('ababahalamaha', ('x', 'ababa', 'y'))
self.assertNotEndsWith(UserString('ababahalamaha'), 'ababa')
self.assertNotEndsWith(UserString('ababahalamaha'), ('x', 'ababa', 'y'))
self.assertNotEndsWith(bytearray(b'ababahalamaha'), b'ababa')
self.assertNotEndsWith(bytearray(b'ababahalamaha'), (b'x', b'ababa', b'y'))
self.assertNotEndsWith(b'ababahalamaha', bytearray(b'ababa'))
self.assertNotEndsWith(b'ababahalamaha',
(bytearray(b'x'), bytearray(b'ababa'), bytearray(b'y')))
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith('ababahalamaha', 'amaha')
self.assertEqual(str(cm.exception),
"'ababahalamaha' ends with 'amaha'")
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith('ababahalamaha', ('x', 'amaha', 'y'))
self.assertEqual(str(cm.exception),
"'ababahalamaha' ends with 'amaha'")
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith(b'ababahalamaha', 'amaha')
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith(b'ababahalamaha', ('ababa', 'amaha'))
self.assertEqual(str(cm.exception), 'Expected str, not bytes')
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith([], 'amaha')
self.assertEqual(str(cm.exception), 'Expected str, not list')
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith('ababahalamaha', b'amaha')
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith('ababahalamaha', (b'ababa', b'amaha'))
self.assertEqual(str(cm.exception), 'Expected bytes, not str')
with self.assertRaises(TypeError):
self.assertNotEndsWith('ababahalamaha', ord('a'))
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith('ababahalamaha', 'amaha', 'abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
with self.assertRaises(self.failureException) as cm:
self.assertNotEndsWith('ababahalamaha', 'amaha', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
def testDeprecatedFailMethods(self):
"""Test that the deprecated fail* methods get removed in 3.12"""
deprecated_names = [

View file

@ -76,7 +76,7 @@ class Test_TestLoader(unittest.TestCase):
loader = unittest.TestLoader()
# This has to be false for the test to succeed
self.assertFalse('runTest'.startswith(loader.testMethodPrefix))
self.assertNotStartsWith('runTest', loader.testMethodPrefix)
suite = loader.loadTestsFromTestCase(Foo)
self.assertIsInstance(suite, loader.suiteClass)

View file

@ -128,14 +128,14 @@ class Test_TestProgram(unittest.TestCase):
argv=["foobar"],
testRunner=unittest.TextTestRunner(stream=stream),
testLoader=self.TestLoader(self.FooBar))
self.assertTrue(hasattr(program, 'result'))
self.assertHasAttr(program, 'result')
out = stream.getvalue()
self.assertIn('\nFAIL: testFail ', out)
self.assertIn('\nERROR: testError ', out)
self.assertIn('\nUNEXPECTED SUCCESS: testUnexpectedSuccess ', out)
expected = ('\n\nFAILED (failures=1, errors=1, skipped=1, '
'expected failures=1, unexpected successes=1)\n')
self.assertTrue(out.endswith(expected))
self.assertEndsWith(out, expected)
@force_not_colorized
def test_Exit(self):
@ -153,7 +153,7 @@ class Test_TestProgram(unittest.TestCase):
self.assertIn('\nUNEXPECTED SUCCESS: testUnexpectedSuccess ', out)
expected = ('\n\nFAILED (failures=1, errors=1, skipped=1, '
'expected failures=1, unexpected successes=1)\n')
self.assertTrue(out.endswith(expected))
self.assertEndsWith(out, expected)
@force_not_colorized
def test_ExitAsDefault(self):
@ -169,7 +169,7 @@ class Test_TestProgram(unittest.TestCase):
self.assertIn('\nUNEXPECTED SUCCESS: testUnexpectedSuccess ', out)
expected = ('\n\nFAILED (failures=1, errors=1, skipped=1, '
'expected failures=1, unexpected successes=1)\n')
self.assertTrue(out.endswith(expected))
self.assertEndsWith(out, expected)
@force_not_colorized
def test_ExitSkippedSuite(self):
@ -182,7 +182,7 @@ class Test_TestProgram(unittest.TestCase):
self.assertEqual(cm.exception.code, 0)
out = stream.getvalue()
expected = '\n\nOK (skipped=1)\n'
self.assertTrue(out.endswith(expected))
self.assertEndsWith(out, expected)
@force_not_colorized
def test_ExitEmptySuite(self):

View file

@ -462,7 +462,7 @@ class Test_TestResult(unittest.TestCase):
self.assertTrue(result.failfast)
result = runner.run(test)
stream.flush()
self.assertTrue(stream.getvalue().endswith('\n\nOK\n'))
self.assertEndsWith(stream.getvalue(), '\n\nOK\n')
class Test_TextTestResult(unittest.TestCase):

View file

@ -586,16 +586,16 @@ class AsyncMagicMethods(unittest.TestCase):
def test_magicmock_has_async_magic_methods(self):
m_mock = MagicMock()
self.assertTrue(hasattr(m_mock, "__aenter__"))
self.assertTrue(hasattr(m_mock, "__aexit__"))
self.assertTrue(hasattr(m_mock, "__anext__"))
self.assertHasAttr(m_mock, "__aenter__")
self.assertHasAttr(m_mock, "__aexit__")
self.assertHasAttr(m_mock, "__anext__")
def test_asyncmock_has_sync_magic_methods(self):
a_mock = AsyncMock()
self.assertTrue(hasattr(a_mock, "__enter__"))
self.assertTrue(hasattr(a_mock, "__exit__"))
self.assertTrue(hasattr(a_mock, "__next__"))
self.assertTrue(hasattr(a_mock, "__len__"))
self.assertHasAttr(a_mock, "__enter__")
self.assertHasAttr(a_mock, "__exit__")
self.assertHasAttr(a_mock, "__next__")
self.assertHasAttr(a_mock, "__len__")
def test_magic_methods_are_async_functions(self):
m_mock = MagicMock()

View file

@ -23,21 +23,21 @@ class TestCallable(unittest.TestCase):
def test_non_callable(self):
for mock in NonCallableMagicMock(), NonCallableMock():
self.assertRaises(TypeError, mock)
self.assertFalse(hasattr(mock, '__call__'))
self.assertNotHasAttr(mock, '__call__')
self.assertIn(mock.__class__.__name__, repr(mock))
def test_hierarchy(self):
self.assertTrue(issubclass(MagicMock, Mock))
self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock))
self.assertIsSubclass(MagicMock, Mock)
self.assertIsSubclass(NonCallableMagicMock, NonCallableMock)
def test_attributes(self):
one = NonCallableMock()
self.assertTrue(issubclass(type(one.one), Mock))
self.assertIsSubclass(type(one.one), Mock)
two = NonCallableMagicMock()
self.assertTrue(issubclass(type(two.two), MagicMock))
self.assertIsSubclass(type(two.two), MagicMock)
def test_subclasses(self):
@ -45,13 +45,13 @@ class TestCallable(unittest.TestCase):
pass
one = MockSub()
self.assertTrue(issubclass(type(one.one), MockSub))
self.assertIsSubclass(type(one.one), MockSub)
class MagicSub(MagicMock):
pass
two = MagicSub()
self.assertTrue(issubclass(type(two.two), MagicSub))
self.assertIsSubclass(type(two.two), MagicSub)
def test_patch_spec(self):

View file

@ -951,7 +951,7 @@ class SpecSignatureTest(unittest.TestCase):
proxy = Foo()
autospec = create_autospec(proxy)
self.assertFalse(hasattr(autospec, '__name__'))
self.assertNotHasAttr(autospec, '__name__')
def test_autospec_signature_staticmethod(self):

View file

@ -10,13 +10,13 @@ class TestMockingMagicMethods(unittest.TestCase):
def test_deleting_magic_methods(self):
mock = Mock()
self.assertFalse(hasattr(mock, '__getitem__'))
self.assertNotHasAttr(mock, '__getitem__')
mock.__getitem__ = Mock()
self.assertTrue(hasattr(mock, '__getitem__'))
self.assertHasAttr(mock, '__getitem__')
del mock.__getitem__
self.assertFalse(hasattr(mock, '__getitem__'))
self.assertNotHasAttr(mock, '__getitem__')
def test_magicmock_del(self):
@ -252,12 +252,12 @@ class TestMockingMagicMethods(unittest.TestCase):
self.assertEqual(list(mock), [1, 2, 3])
getattr(mock, '__bool__').return_value = False
self.assertFalse(hasattr(mock, '__nonzero__'))
self.assertNotHasAttr(mock, '__nonzero__')
self.assertFalse(bool(mock))
for entry in _magics:
self.assertTrue(hasattr(mock, entry))
self.assertFalse(hasattr(mock, '__imaginary__'))
self.assertHasAttr(mock, entry)
self.assertNotHasAttr(mock, '__imaginary__')
def test_magic_mock_equality(self):

View file

@ -2215,13 +2215,13 @@ class MockTest(unittest.TestCase):
def test_attribute_deletion(self):
for mock in (Mock(), MagicMock(), NonCallableMagicMock(),
NonCallableMock()):
self.assertTrue(hasattr(mock, 'm'))
self.assertHasAttr(mock, 'm')
del mock.m
self.assertFalse(hasattr(mock, 'm'))
self.assertNotHasAttr(mock, 'm')
del mock.f
self.assertFalse(hasattr(mock, 'f'))
self.assertNotHasAttr(mock, 'f')
self.assertRaises(AttributeError, getattr, mock, 'f')
@ -2230,18 +2230,18 @@ class MockTest(unittest.TestCase):
for mock in (Mock(), MagicMock(), NonCallableMagicMock(),
NonCallableMock()):
mock.foo = 3
self.assertTrue(hasattr(mock, 'foo'))
self.assertHasAttr(mock, 'foo')
self.assertEqual(mock.foo, 3)
del mock.foo
self.assertFalse(hasattr(mock, 'foo'))
self.assertNotHasAttr(mock, 'foo')
mock.foo = 4
self.assertTrue(hasattr(mock, 'foo'))
self.assertHasAttr(mock, 'foo')
self.assertEqual(mock.foo, 4)
del mock.foo
self.assertFalse(hasattr(mock, 'foo'))
self.assertNotHasAttr(mock, 'foo')
def test_mock_raises_when_deleting_nonexistent_attribute(self):
@ -2259,7 +2259,7 @@ class MockTest(unittest.TestCase):
mock.child = True
del mock.child
mock.reset_mock()
self.assertFalse(hasattr(mock, 'child'))
self.assertNotHasAttr(mock, 'child')
def test_class_assignable(self):

View file

@ -366,7 +366,7 @@ class PatchTest(unittest.TestCase):
self.assertEqual(SomeClass.frooble, sentinel.Frooble)
test()
self.assertFalse(hasattr(SomeClass, 'frooble'))
self.assertNotHasAttr(SomeClass, 'frooble')
def test_patch_wont_create_by_default(self):
@ -383,7 +383,7 @@ class PatchTest(unittest.TestCase):
@patch.object(SomeClass, 'ord', sentinel.Frooble)
def test(): pass
test()
self.assertFalse(hasattr(SomeClass, 'ord'))
self.assertNotHasAttr(SomeClass, 'ord')
def test_patch_builtins_without_create(self):
@ -1477,7 +1477,7 @@ class PatchTest(unittest.TestCase):
finally:
patcher.stop()
self.assertFalse(hasattr(Foo, 'blam'))
self.assertNotHasAttr(Foo, 'blam')
def test_patch_multiple_spec_set(self):

View file

@ -111,10 +111,6 @@ class BaseTest(unittest.TestCase):
result = f.read()
return result
def assertEndsWith(self, string, tail):
if not string.endswith(tail):
self.fail(f"String {string!r} does not end with {tail!r}")
class BasicTest(BaseTest):
"""Test venv module functionality."""

View file

@ -1321,13 +1321,67 @@ class TestCase(object):
"""Same as self.assertTrue(isinstance(obj, cls)), with a nicer
default message."""
if not isinstance(obj, cls):
standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls)
if isinstance(cls, tuple):
standardMsg = f'{safe_repr(obj)} is not an instance of any of {cls!r}'
else:
standardMsg = f'{safe_repr(obj)} is not an instance of {cls!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertNotIsInstance(self, obj, cls, msg=None):
"""Included for symmetry with assertIsInstance."""
if isinstance(obj, cls):
standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls)
if isinstance(cls, tuple):
for x in cls:
if isinstance(obj, x):
cls = x
break
standardMsg = f'{safe_repr(obj)} is an instance of {cls!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertIsSubclass(self, cls, superclass, msg=None):
try:
if issubclass(cls, superclass):
return
except TypeError:
if not isinstance(cls, type):
self.fail(self._formatMessage(msg, f'{cls!r} is not a class'))
raise
if isinstance(superclass, tuple):
standardMsg = f'{cls!r} is not a subclass of any of {superclass!r}'
else:
standardMsg = f'{cls!r} is not a subclass of {superclass!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertNotIsSubclass(self, cls, superclass, msg=None):
try:
if not issubclass(cls, superclass):
return
except TypeError:
if not isinstance(cls, type):
self.fail(self._formatMessage(msg, f'{cls!r} is not a class'))
raise
if isinstance(superclass, tuple):
for x in superclass:
if issubclass(cls, x):
superclass = x
break
standardMsg = f'{cls!r} is a subclass of {superclass!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertHasAttr(self, obj, name, msg=None):
if not hasattr(obj, name):
if isinstance(obj, types.ModuleType):
standardMsg = f'module {obj.__name__!r} has no attribute {name!r}'
else:
standardMsg = f'{type(obj).__name__} instance has no attribute {name!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertNotHasAttr(self, obj, name, msg=None):
if hasattr(obj, name):
if isinstance(obj, types.ModuleType):
standardMsg = f'module {obj.__name__!r} has unexpected attribute {name!r}'
else:
standardMsg = f'{type(obj).__name__} instance has unexpected attribute {name!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertRaisesRegex(self, expected_exception, expected_regex,
@ -1391,6 +1445,80 @@ class TestCase(object):
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
def _tail_type_check(self, s, tails, msg):
if not isinstance(tails, tuple):
tails = (tails,)
for tail in tails:
if isinstance(tail, str):
if not isinstance(s, str):
self.fail(self._formatMessage(msg,
f'Expected str, not {type(s).__name__}'))
elif isinstance(tail, (bytes, bytearray)):
if not isinstance(s, (bytes, bytearray)):
self.fail(self._formatMessage(msg,
f'Expected bytes, not {type(s).__name__}'))
def assertStartsWith(self, s, prefix, msg=None):
try:
if s.startswith(prefix):
return
except (AttributeError, TypeError):
self._tail_type_check(s, prefix, msg)
raise
a = safe_repr(s, short=True)
b = safe_repr(prefix)
if isinstance(prefix, tuple):
standardMsg = f"{a} doesn't start with any of {b}"
else:
standardMsg = f"{a} doesn't start with {b}"
self.fail(self._formatMessage(msg, standardMsg))
def assertNotStartsWith(self, s, prefix, msg=None):
try:
if not s.startswith(prefix):
return
except (AttributeError, TypeError):
self._tail_type_check(s, prefix, msg)
raise
if isinstance(prefix, tuple):
for x in prefix:
if s.startswith(x):
prefix = x
break
a = safe_repr(s, short=True)
b = safe_repr(prefix)
self.fail(self._formatMessage(msg, f"{a} starts with {b}"))
def assertEndsWith(self, s, suffix, msg=None):
try:
if s.endswith(suffix):
return
except (AttributeError, TypeError):
self._tail_type_check(s, suffix, msg)
raise
a = safe_repr(s, short=True)
b = safe_repr(suffix)
if isinstance(suffix, tuple):
standardMsg = f"{a} doesn't end with any of {b}"
else:
standardMsg = f"{a} doesn't end with {b}"
self.fail(self._formatMessage(msg, standardMsg))
def assertNotEndsWith(self, s, suffix, msg=None):
try:
if not s.endswith(suffix):
return
except (AttributeError, TypeError):
self._tail_type_check(s, suffix, msg)
raise
if isinstance(suffix, tuple):
for x in suffix:
if s.endswith(x):
suffix = x
break
a = safe_repr(s, short=True)
b = safe_repr(suffix)
self.fail(self._formatMessage(msg, f"{a} ends with {b}"))
class FunctionTestCase(TestCase):

View file

@ -0,0 +1,9 @@
Add new assertion methods for :mod:`unittest`:
:meth:`~unittest.TestCase.assertHasAttr`,
:meth:`~unittest.TestCase.assertNotHasAttr`,
:meth:`~unittest.TestCase.assertIsSubclass`,
:meth:`~unittest.TestCase.assertNotIsSubclass`
:meth:`~unittest.TestCase.assertStartsWith`,
:meth:`~unittest.TestCase.assertNotStartsWith`,
:meth:`~unittest.TestCase.assertEndsWith` and
:meth:`~unittest.TestCase.assertNotEndsWith`.