mirror of
https://github.com/python/cpython.git
synced 2025-11-02 03:01:58 +00:00
Issue #16997: unittest.TestCase now provides a subTest() context manager to procedurally generate, in an easy way, small test instances.
This commit is contained in:
parent
a612176c9c
commit
c9b3ef2df0
10 changed files with 540 additions and 106 deletions
|
|
@ -41,7 +41,7 @@ class TestHashing(object):
|
|||
self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e))
|
||||
|
||||
|
||||
class LoggingResult(unittest.TestResult):
|
||||
class _BaseLoggingResult(unittest.TestResult):
|
||||
def __init__(self, log):
|
||||
self._events = log
|
||||
super().__init__()
|
||||
|
|
@ -52,7 +52,7 @@ class LoggingResult(unittest.TestResult):
|
|||
|
||||
def startTestRun(self):
|
||||
self._events.append('startTestRun')
|
||||
super(LoggingResult, self).startTestRun()
|
||||
super().startTestRun()
|
||||
|
||||
def stopTest(self, test):
|
||||
self._events.append('stopTest')
|
||||
|
|
@ -60,7 +60,7 @@ class LoggingResult(unittest.TestResult):
|
|||
|
||||
def stopTestRun(self):
|
||||
self._events.append('stopTestRun')
|
||||
super(LoggingResult, self).stopTestRun()
|
||||
super().stopTestRun()
|
||||
|
||||
def addFailure(self, *args):
|
||||
self._events.append('addFailure')
|
||||
|
|
@ -68,7 +68,7 @@ class LoggingResult(unittest.TestResult):
|
|||
|
||||
def addSuccess(self, *args):
|
||||
self._events.append('addSuccess')
|
||||
super(LoggingResult, self).addSuccess(*args)
|
||||
super().addSuccess(*args)
|
||||
|
||||
def addError(self, *args):
|
||||
self._events.append('addError')
|
||||
|
|
@ -76,15 +76,39 @@ class LoggingResult(unittest.TestResult):
|
|||
|
||||
def addSkip(self, *args):
|
||||
self._events.append('addSkip')
|
||||
super(LoggingResult, self).addSkip(*args)
|
||||
super().addSkip(*args)
|
||||
|
||||
def addExpectedFailure(self, *args):
|
||||
self._events.append('addExpectedFailure')
|
||||
super(LoggingResult, self).addExpectedFailure(*args)
|
||||
super().addExpectedFailure(*args)
|
||||
|
||||
def addUnexpectedSuccess(self, *args):
|
||||
self._events.append('addUnexpectedSuccess')
|
||||
super(LoggingResult, self).addUnexpectedSuccess(*args)
|
||||
super().addUnexpectedSuccess(*args)
|
||||
|
||||
|
||||
class LegacyLoggingResult(_BaseLoggingResult):
|
||||
"""
|
||||
A legacy TestResult implementation, without an addSubTest method,
|
||||
which records its method calls.
|
||||
"""
|
||||
|
||||
@property
|
||||
def addSubTest(self):
|
||||
raise AttributeError
|
||||
|
||||
|
||||
class LoggingResult(_BaseLoggingResult):
|
||||
"""
|
||||
A TestResult implementation which records its method calls.
|
||||
"""
|
||||
|
||||
def addSubTest(self, test, subtest, err):
|
||||
if err is None:
|
||||
self._events.append('addSubTestSuccess')
|
||||
else:
|
||||
self._events.append('addSubTestFailure')
|
||||
super().addSubTest(test, subtest, err)
|
||||
|
||||
|
||||
class ResultWithNoStartTestRunStopTestRun(object):
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from test import support
|
|||
import unittest
|
||||
|
||||
from .support import (
|
||||
TestEquality, TestHashing, LoggingResult,
|
||||
TestEquality, TestHashing, LoggingResult, LegacyLoggingResult,
|
||||
ResultWithNoStartTestRunStopTestRun
|
||||
)
|
||||
|
||||
|
|
@ -297,6 +297,98 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
|
|||
|
||||
Foo('test').run()
|
||||
|
||||
def _check_call_order__subtests(self, result, events, expected_events):
|
||||
class Foo(Test.LoggingTestCase):
|
||||
def test(self):
|
||||
super(Foo, self).test()
|
||||
for i in [1, 2, 3]:
|
||||
with self.subTest(i=i):
|
||||
if i == 1:
|
||||
self.fail('failure')
|
||||
for j in [2, 3]:
|
||||
with self.subTest(j=j):
|
||||
if i * j == 6:
|
||||
raise RuntimeError('raised by Foo.test')
|
||||
1 / 0
|
||||
|
||||
# Order is the following:
|
||||
# i=1 => subtest failure
|
||||
# i=2, j=2 => subtest success
|
||||
# i=2, j=3 => subtest error
|
||||
# i=3, j=2 => subtest error
|
||||
# i=3, j=3 => subtest success
|
||||
# toplevel => error
|
||||
Foo(events).run(result)
|
||||
self.assertEqual(events, expected_events)
|
||||
|
||||
def test_run_call_order__subtests(self):
|
||||
events = []
|
||||
result = LoggingResult(events)
|
||||
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||
'addSubTestFailure', 'addSubTestSuccess',
|
||||
'addSubTestFailure', 'addSubTestFailure',
|
||||
'addSubTestSuccess', 'addError', 'stopTest']
|
||||
self._check_call_order__subtests(result, events, expected)
|
||||
|
||||
def test_run_call_order__subtests_legacy(self):
|
||||
# With a legacy result object (without a addSubTest method),
|
||||
# text execution stops after the first subtest failure.
|
||||
events = []
|
||||
result = LegacyLoggingResult(events)
|
||||
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||
'addFailure', 'stopTest']
|
||||
self._check_call_order__subtests(result, events, expected)
|
||||
|
||||
def _check_call_order__subtests_success(self, result, events, expected_events):
|
||||
class Foo(Test.LoggingTestCase):
|
||||
def test(self):
|
||||
super(Foo, self).test()
|
||||
for i in [1, 2]:
|
||||
with self.subTest(i=i):
|
||||
for j in [2, 3]:
|
||||
with self.subTest(j=j):
|
||||
pass
|
||||
|
||||
Foo(events).run(result)
|
||||
self.assertEqual(events, expected_events)
|
||||
|
||||
def test_run_call_order__subtests_success(self):
|
||||
events = []
|
||||
result = LoggingResult(events)
|
||||
# The 6 subtest successes are individually recorded, in addition
|
||||
# to the whole test success.
|
||||
expected = (['startTest', 'setUp', 'test', 'tearDown']
|
||||
+ 6 * ['addSubTestSuccess']
|
||||
+ ['addSuccess', 'stopTest'])
|
||||
self._check_call_order__subtests_success(result, events, expected)
|
||||
|
||||
def test_run_call_order__subtests_success_legacy(self):
|
||||
# With a legacy result, only the whole test success is recorded.
|
||||
events = []
|
||||
result = LegacyLoggingResult(events)
|
||||
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||
'addSuccess', 'stopTest']
|
||||
self._check_call_order__subtests_success(result, events, expected)
|
||||
|
||||
def test_run_call_order__subtests_failfast(self):
|
||||
events = []
|
||||
result = LoggingResult(events)
|
||||
result.failfast = True
|
||||
|
||||
class Foo(Test.LoggingTestCase):
|
||||
def test(self):
|
||||
super(Foo, self).test()
|
||||
with self.subTest(i=1):
|
||||
self.fail('failure')
|
||||
with self.subTest(i=2):
|
||||
self.fail('failure')
|
||||
self.fail('failure')
|
||||
|
||||
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||
'addSubTestFailure', 'stopTest']
|
||||
Foo(events).run(result)
|
||||
self.assertEqual(events, expected)
|
||||
|
||||
# "This class attribute gives the exception raised by the test() method.
|
||||
# If a test framework needs to use a specialized exception, possibly to
|
||||
# carry additional information, it must subclass this exception in
|
||||
|
|
|
|||
|
|
@ -234,6 +234,37 @@ class Test_TestResult(unittest.TestCase):
|
|||
'testGetDescriptionWithoutDocstring (' + __name__ +
|
||||
'.Test_TestResult)')
|
||||
|
||||
def testGetSubTestDescriptionWithoutDocstring(self):
|
||||
with self.subTest(foo=1, bar=2):
|
||||
result = unittest.TextTestResult(None, True, 1)
|
||||
self.assertEqual(
|
||||
result.getDescription(self._subtest),
|
||||
'testGetSubTestDescriptionWithoutDocstring (' + __name__ +
|
||||
'.Test_TestResult) (bar=2, foo=1)')
|
||||
with self.subTest('some message'):
|
||||
result = unittest.TextTestResult(None, True, 1)
|
||||
self.assertEqual(
|
||||
result.getDescription(self._subtest),
|
||||
'testGetSubTestDescriptionWithoutDocstring (' + __name__ +
|
||||
'.Test_TestResult) [some message]')
|
||||
|
||||
def testGetSubTestDescriptionWithoutDocstringAndParams(self):
|
||||
with self.subTest():
|
||||
result = unittest.TextTestResult(None, True, 1)
|
||||
self.assertEqual(
|
||||
result.getDescription(self._subtest),
|
||||
'testGetSubTestDescriptionWithoutDocstringAndParams '
|
||||
'(' + __name__ + '.Test_TestResult) (<subtest>)')
|
||||
|
||||
def testGetNestedSubTestDescriptionWithoutDocstring(self):
|
||||
with self.subTest(foo=1):
|
||||
with self.subTest(bar=2):
|
||||
result = unittest.TextTestResult(None, True, 1)
|
||||
self.assertEqual(
|
||||
result.getDescription(self._subtest),
|
||||
'testGetNestedSubTestDescriptionWithoutDocstring '
|
||||
'(' + __name__ + '.Test_TestResult) (bar=2, foo=1)')
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2,
|
||||
"Docstrings are omitted with -O2 and above")
|
||||
def testGetDescriptionWithOneLineDocstring(self):
|
||||
|
|
@ -245,6 +276,18 @@ class Test_TestResult(unittest.TestCase):
|
|||
'(' + __name__ + '.Test_TestResult)\n'
|
||||
'Tests getDescription() for a method with a docstring.'))
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2,
|
||||
"Docstrings are omitted with -O2 and above")
|
||||
def testGetSubTestDescriptionWithOneLineDocstring(self):
|
||||
"""Tests getDescription() for a method with a docstring."""
|
||||
result = unittest.TextTestResult(None, True, 1)
|
||||
with self.subTest(foo=1, bar=2):
|
||||
self.assertEqual(
|
||||
result.getDescription(self._subtest),
|
||||
('testGetSubTestDescriptionWithOneLineDocstring '
|
||||
'(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n'
|
||||
'Tests getDescription() for a method with a docstring.'))
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2,
|
||||
"Docstrings are omitted with -O2 and above")
|
||||
def testGetDescriptionWithMultiLineDocstring(self):
|
||||
|
|
@ -259,6 +302,21 @@ class Test_TestResult(unittest.TestCase):
|
|||
'Tests getDescription() for a method with a longer '
|
||||
'docstring.'))
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2,
|
||||
"Docstrings are omitted with -O2 and above")
|
||||
def testGetSubTestDescriptionWithMultiLineDocstring(self):
|
||||
"""Tests getDescription() for a method with a longer docstring.
|
||||
The second line of the docstring.
|
||||
"""
|
||||
result = unittest.TextTestResult(None, True, 1)
|
||||
with self.subTest(foo=1, bar=2):
|
||||
self.assertEqual(
|
||||
result.getDescription(self._subtest),
|
||||
('testGetSubTestDescriptionWithMultiLineDocstring '
|
||||
'(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n'
|
||||
'Tests getDescription() for a method with a longer '
|
||||
'docstring.'))
|
||||
|
||||
def testStackFrameTrimming(self):
|
||||
class Frame(object):
|
||||
class tb_frame(object):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pickle
|
|||
import subprocess
|
||||
|
||||
import unittest
|
||||
from unittest.case import _Outcome
|
||||
|
||||
from .support import LoggingResult, ResultWithNoStartTestRunStopTestRun
|
||||
|
||||
|
|
@ -42,12 +43,8 @@ class TestCleanUp(unittest.TestCase):
|
|||
def testNothing(self):
|
||||
pass
|
||||
|
||||
class MockOutcome(object):
|
||||
success = True
|
||||
errors = []
|
||||
|
||||
test = TestableTest('testNothing')
|
||||
test._outcomeForDoCleanups = MockOutcome
|
||||
outcome = test._outcome = _Outcome()
|
||||
|
||||
exc1 = Exception('foo')
|
||||
exc2 = Exception('bar')
|
||||
|
|
@ -61,9 +58,10 @@ class TestCleanUp(unittest.TestCase):
|
|||
test.addCleanup(cleanup2)
|
||||
|
||||
self.assertFalse(test.doCleanups())
|
||||
self.assertFalse(MockOutcome.success)
|
||||
self.assertFalse(outcome.success)
|
||||
|
||||
(Type1, instance1, _), (Type2, instance2, _) = reversed(MockOutcome.errors)
|
||||
((_, (Type1, instance1, _)),
|
||||
(_, (Type2, instance2, _))) = reversed(outcome.errors)
|
||||
self.assertEqual((Type1, instance1), (Exception, exc1))
|
||||
self.assertEqual((Type2, instance2), (Exception, exc2))
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,31 @@ class Test_TestSkipping(unittest.TestCase):
|
|||
self.assertEqual(result.skipped, [(test, "testing")])
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
|
||||
def test_skipping_subtests(self):
|
||||
class Foo(unittest.TestCase):
|
||||
def test_skip_me(self):
|
||||
with self.subTest(a=1):
|
||||
with self.subTest(b=2):
|
||||
self.skipTest("skip 1")
|
||||
self.skipTest("skip 2")
|
||||
self.skipTest("skip 3")
|
||||
events = []
|
||||
result = LoggingResult(events)
|
||||
test = Foo("test_skip_me")
|
||||
test.run(result)
|
||||
self.assertEqual(events, ['startTest', 'addSkip', 'addSkip',
|
||||
'addSkip', 'stopTest'])
|
||||
self.assertEqual(len(result.skipped), 3)
|
||||
subtest, msg = result.skipped[0]
|
||||
self.assertEqual(msg, "skip 1")
|
||||
self.assertIsInstance(subtest, unittest.TestCase)
|
||||
self.assertIsNot(subtest, test)
|
||||
subtest, msg = result.skipped[1]
|
||||
self.assertEqual(msg, "skip 2")
|
||||
self.assertIsInstance(subtest, unittest.TestCase)
|
||||
self.assertIsNot(subtest, test)
|
||||
self.assertEqual(result.skipped[2], (test, "skip 3"))
|
||||
|
||||
def test_skipping_decorators(self):
|
||||
op_table = ((unittest.skipUnless, False, True),
|
||||
(unittest.skipIf, True, False))
|
||||
|
|
@ -95,6 +120,31 @@ class Test_TestSkipping(unittest.TestCase):
|
|||
self.assertEqual(result.expectedFailures[0][0], test)
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
|
||||
def test_expected_failure_subtests(self):
|
||||
# A failure in any subtest counts as the expected failure of the
|
||||
# whole test.
|
||||
class Foo(unittest.TestCase):
|
||||
@unittest.expectedFailure
|
||||
def test_die(self):
|
||||
with self.subTest():
|
||||
# This one succeeds
|
||||
pass
|
||||
with self.subTest():
|
||||
self.fail("help me!")
|
||||
with self.subTest():
|
||||
# This one doesn't get executed
|
||||
self.fail("shouldn't come here")
|
||||
events = []
|
||||
result = LoggingResult(events)
|
||||
test = Foo("test_die")
|
||||
test.run(result)
|
||||
self.assertEqual(events,
|
||||
['startTest', 'addSubTestSuccess',
|
||||
'addExpectedFailure', 'stopTest'])
|
||||
self.assertEqual(len(result.expectedFailures), 1)
|
||||
self.assertIs(result.expectedFailures[0][0], test)
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
|
||||
def test_unexpected_success(self):
|
||||
class Foo(unittest.TestCase):
|
||||
@unittest.expectedFailure
|
||||
|
|
@ -110,6 +160,30 @@ class Test_TestSkipping(unittest.TestCase):
|
|||
self.assertEqual(result.unexpectedSuccesses, [test])
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
|
||||
def test_unexpected_success_subtests(self):
|
||||
# Success in all subtests counts as the unexpected success of
|
||||
# the whole test.
|
||||
class Foo(unittest.TestCase):
|
||||
@unittest.expectedFailure
|
||||
def test_die(self):
|
||||
with self.subTest():
|
||||
# This one succeeds
|
||||
pass
|
||||
with self.subTest():
|
||||
# So does this one
|
||||
pass
|
||||
events = []
|
||||
result = LoggingResult(events)
|
||||
test = Foo("test_die")
|
||||
test.run(result)
|
||||
self.assertEqual(events,
|
||||
['startTest',
|
||||
'addSubTestSuccess', 'addSubTestSuccess',
|
||||
'addUnexpectedSuccess', 'stopTest'])
|
||||
self.assertFalse(result.failures)
|
||||
self.assertEqual(result.unexpectedSuccesses, [test])
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
|
||||
def test_skip_doesnt_run_setup(self):
|
||||
class Foo(unittest.TestCase):
|
||||
wasSetUp = False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue