Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
b4a81c83
Commit
b4a81c83
authored
May 29, 2009
by
Michael Foord
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add test discovery to unittest. Issue 6001.
parent
fe6e784a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
630 additions
and
22 deletions
+630
-22
Doc/library/unittest.rst
Doc/library/unittest.rst
+153
-14
Lib/test/test_unittest.py
Lib/test/test_unittest.py
+296
-1
Lib/unittest.py
Lib/unittest.py
+178
-7
Misc/NEWS
Misc/NEWS
+3
-0
No files found.
Doc/library/unittest.rst
View file @
b4a81c83
...
...
@@ -90,6 +90,9 @@ need to derive from a specific class.
`python-mock <http://python-mock.sourceforge.net/>`_ and `minimock <http://blog.ianbicking.org/minimock.html>`_
Tools for creating mock test objects (objects simulating external resources).
.. _unittest-command-line-interface:
Command Line Interface
----------------------
...
...
@@ -100,8 +103,8 @@ modules, classes or even individual test methods::
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method
You can pass in a list with any combination of module names, and fully
qualified class or
method names.
You can pass in a list with any combination of module names, and fully
qualified class or
method names.
You can run tests with more detail (higher verbosity) by passing in the -v flag::
...
...
@@ -111,9 +114,47 @@ For a list of all the command line options::
python -m unittest -h
.. versionchanged:: 27
In earlier versions it was only possible to run individual test methods and not modules
or classes.
.. versionchanged:: 2.7
In earlier versions it was only possible to run individual test methods and
not modules or classes.
The command line can also be used for test discovery, for running all of the
tests in a project or just a subset.
.. _unittest-test-discovery:
Test Discovery
--------------
.. versionadded:: 2.7
unittest supports simple test discovery. For a project's tests to be
compatible with test discovery they must all be importable from the top level
directory of the project; i.e. they must all be in Python packages.
Test discovery is implemented in :meth:`TestLoader.discover`, but can also be
used from the command line. The basic command line usage is::
cd project_directory
python -m unittest discover
The ``discover`` sub-command has the following options:
-v, --verbose Verbose output
-s directory Directory to start discovery ('.' default)
-p pattern Pattern to match test files ('test*.py' default)
-t directory Top level directory of project (default to
start directory)
The -s, -p, & -t options can be passsed in as positional arguments. The
following two command lines are equivalent::
python -m unittest -s project_directory -p '*_test.py'
python -m unittest project_directory '*_test.py'
Test modules and packages can customize test loading and discovery by through
the `load_tests protocol`_.
.. _unittest-minimal-example:
...
...
@@ -1151,6 +1192,13 @@ Loading and running tests
directly does not play well with this method. Doing so, however, can
be useful when the fixtures are different and defined in subclasses.
If a module provides a ``load_tests`` function it will be called to
load the tests. This allows modules to customize test loading.
This is the `load_tests protocol`_.
.. versionchanged:: 2.7
Support for ``load_tests`` added.
.. method:: loadTestsFromName(name[, module])
...
...
@@ -1165,13 +1213,14 @@ Loading and running tests
rather than "a callable object".
For example, if you have a module :mod:`SampleTests` containing a
:class:`TestCase`\ -derived class :class:`SampleTestCase` with three test
methods (:meth:`test_one`, :meth:`test_two`, and :meth:`test_three`), the
specifier ``'SampleTests.SampleTestCase'`` would cause this method to return a
suite which will run all three test methods. Using the specifier
``'SampleTests.SampleTestCase.test_two'`` would cause it to return a test suite
which will run only the :meth:`test_two` test method. The specifier can refer
to modules and packages which have not been imported; they will be imported as a
:class:`TestCase`\ -derived class :class:`SampleTestCase` with three
test methods (:meth:`test_one`, :meth:`test_two`, and
:meth:`test_three`), the specifier ``'SampleTests.SampleTestCase'``
would cause this method to return a suite which will run all three test
methods. Using the specifier ``'SampleTests.SampleTestCase.test_two'``
would cause it to return a test suite which will run only the
:meth:`test_two` test method. The specifier can refer to modules and
packages which have not been imported; they will be imported as a
side-effect.
The method optionally resolves *name* relative to the given *module*.
...
...
@@ -1189,6 +1238,31 @@ Loading and running tests
Return a sorted sequence of method names found within *testCaseClass*;
this should be a subclass of :class:`TestCase`.
.. method:: discover(start_dir, pattern='test*.py', top_level_dir=None)
Find and return all test modules from the specified start directory,
recursing into subdirectories to find them. Only test files that match
*pattern* will be loaded. (Using shell style pattern matching.)
All test modules must be importable from the top level of the project. If
the start directory is not the top level directory then the top level
directory must be specified separately.
If a test package name (directory with :file:`__init__.py`) matches the
pattern then the package will be checked for a ``load_tests``
function. If this exists then it will be called with *loader*, *tests*,
*pattern*.
If load_tests exists then discovery does *not* recurse into the package,
``load_tests`` is responsible for loading all tests in the package.
The pattern is deliberately not stored as a loader attribute so that
packages can continue discovery themselves. *top_level_dir* is stored so
``load_tests`` does not need to pass this argument in to
``loader.discover()``.
The following attributes of a :class:`TestLoader` can be configured either by
subclassing or assignment on an instance:
...
...
@@ -1353,8 +1427,8 @@ Loading and running tests
.. method:: addFailure(test, err)
Called when the test case *test* signals a failure. *err* is a tuple of
the form
returned by :func:`sys.exc_info`:
``(type, value, traceback)``.
Called when the test case *test* signals a failure. *err* is a tuple of
the form returned by :func:`sys.exc_info`:
``(type, value, traceback)``.
The default implementation appends a tuple ``(test, formatted_err)`` to
the instance's :attr:`failures` attribute, where *formatted_err* is a
...
...
@@ -1447,3 +1521,68 @@ Loading and running tests
.. versionchanged:: 2.7
The ``exit`` and ``verbosity`` parameters were added.
load_tests Protocol
###################
Modules or packages can customize how tests are loaded from them during normal
test runs or test discovery by implementing a function called ``load_tests``.
If a test module defines ``load_tests`` it will be called by
:meth:`TestLoader.loadTestsFromModule` with the following arguments::
load_tests(loader, standard_tests, None)
It should return a :class:`TestSuite`.
*loader* is the instance of :class:`TestLoader` doing the loading.
*standard_tests* are the tests that would be loaded by default from the
module. It is common for test modules to only want to add or remove tests
from the standard set of tests.
The third argument is used when loading packages as part of test discovery.
A typical ``load_tests`` function that loads tests from a specific set of
:class:`TestCase` classes may look like::
test_cases = (TestCase1, TestCase2, TestCase3)
def load_tests(loader, tests, pattern):
suite = TestSuite()
for test_class in test_cases:
tests = loader.loadTestsFromTestCase(test_class)
suite.addTests(tests)
return suite
If discovery is started, either from the command line or by calling
:meth:`TestLoader.discover`, with a pattern that matches a package
name then the package :file:`__init__.py` will be checked for ``load_tests``.
.. note::
The default pattern is 'test*.py'. This matches all python files
that start with 'test' but *won't* match any test directories.
A pattern like 'test*' will match test packages as well as
modules.
If the package :file:`__init__.py` defines ``load_tests`` then it will be
called and discovery not continued into the package. ``load_tests``
is called with the following arguments::
load_tests(loader, standard_tests, pattern)
This should return a :class:`TestSuite` representing all the tests
from the package. (``standard_tests`` will only contain tests
collected from :file:`__init__.py`.)
Because the pattern is passed into ``load_tests`` the package is free to
continue (and potentially modify) test discovery. A 'do nothing'
``load_tests`` function for a test package would look like::
def load_tests(loader, standard_tests, pattern):
# top level directory cached on loader instance
this_dir = os.path.dirname(__file__)
package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
standard_tests.addTests(package_tests)
return standard_tests
Lib/test/test_unittest.py
View file @
b4a81c83
...
...
@@ -7,7 +7,9 @@ Still need testing:
"""
from
StringIO
import
StringIO
import
os
import
re
import
sys
from
test
import
test_support
import
unittest
from
unittest
import
TestCase
,
TestProgram
...
...
@@ -256,6 +258,30 @@ class Test_TestLoader(TestCase):
reference
=
[
unittest
.
TestSuite
([
MyTestCase
(
'test'
)])]
self
.
assertEqual
(
list
(
suite
),
reference
)
# Check that loadTestsFromModule honors (or not) a module
# with a load_tests function.
def
test_loadTestsFromModule__load_tests
(
self
):
m
=
types
.
ModuleType
(
'm'
)
class
MyTestCase
(
unittest
.
TestCase
):
def
test
(
self
):
pass
m
.
testcase_1
=
MyTestCase
load_tests_args
=
[]
def
load_tests
(
loader
,
tests
,
pattern
):
load_tests_args
.
extend
((
loader
,
tests
,
pattern
))
return
tests
m
.
load_tests
=
load_tests
loader
=
unittest
.
TestLoader
()
suite
=
loader
.
loadTestsFromModule
(
m
)
self
.
assertEquals
(
load_tests_args
,
[
loader
,
suite
,
None
])
load_tests_args
=
[]
suite
=
loader
.
loadTestsFromModule
(
m
,
use_load_tests
=
False
)
self
.
assertEquals
(
load_tests_args
,
[])
################################################################
### /Tests for TestLoader.loadTestsFromModule()
...
...
@@ -3379,6 +3405,275 @@ class Test_TextTestRunner(TestCase):
self.assertEqual(events, expected)
class TestDiscovery(TestCase):
# Heavily mocked tests so I can avoid hitting the filesystem
def test_get_module_from_path(self):
loader = unittest.TestLoader()
def restore_import():
unittest.__import__ = __import__
unittest.__import__ = lambda *_: None
self.addCleanup(restore_import)
expected_module = object()
def del_module():
del sys.modules['bar.baz']
sys.modules['bar.baz'] = expected_module
self.addCleanup(del_module)
loader._top_level_dir = '/foo'
module = loader._get_module_from_path('/foo/bar/baz.py')
self.assertEqual(module, expected_module)
if not __debug__:
# asserts are off
return
with self.assertRaises(AssertionError):
loader._get_module_from_path('/bar/baz.py')
def test_find_tests(self):
loader = unittest.TestLoader()
original_listdir = os.listdir
def restore_listdir():
os.listdir = original_listdir
original_isfile = os.path.isfile
def restore_isfile():
os.path.isfile = original_isfile
original_isdir = os.path.isdir
def restore_isdir():
os.path.isdir = original_isdir
path_lists = [['test1.py', 'test2.py', 'not_a_test.py', 'test_dir',
'test.foo', 'another_dir'],
['test3.py', 'test4.py', ]]
os.listdir = lambda path: path_lists.pop(0)
self.addCleanup(restore_listdir)
def isdir(path):
return path.endswith('dir')
os.path.isdir = isdir
self.addCleanup(restore_isdir)
def isfile(path):
# another_dir is not a package and so shouldn't be recursed into
return not path.endswith('dir') and not 'another_dir' in path
os.path.isfile = isfile
self.addCleanup(restore_isfile)
loader._get_module_from_path = lambda path: path + ' module'
loader.loadTestsFromModule = lambda module: module + ' tests'
loader._top_level_dir = '/foo'
suite = list(loader._find_tests('/foo', 'test*.py'))
expected = [os.path.join('/foo', name) + ' module tests' for name in
('test1.py', 'test2.py')]
expected.extend([os.path.join('/foo', 'test_dir', name) + ' module tests' for name in
('test3.py', 'test4.py')])
self.assertEqual(suite, expected)
def test_find_tests_with_package(self):
loader = unittest.TestLoader()
original_listdir = os.listdir
def restore_listdir():
os.listdir = original_listdir
original_isfile = os.path.isfile
def restore_isfile():
os.path.isfile = original_isfile
original_isdir = os.path.isdir
def restore_isdir():
os.path.isdir = original_isdir
directories = ['a_directory', 'test_directory', 'test_directory2']
path_lists = [directories, [], [], []]
os.listdir = lambda path: path_lists.pop(0)
self.addCleanup(restore_listdir)
os.path.isdir = lambda path: True
self.addCleanup(restore_isdir)
os.path.isfile = lambda path: os.path.basename(path) not in directories
self.addCleanup(restore_isfile)
class Module(object):
paths = []
load_tests_args = []
def __init__(self, path):
self.path = path
self.paths.append(path)
if os.path.basename(path) == 'test_directory':
def load_tests(loader, tests, pattern):
self.load_tests_args.append((loader, tests, pattern))
return 'load_tests'
self.load_tests = load_tests
def __eq__(self, other):
return self.path == other.path
loader._get_module_from_path = lambda path: Module(path)
def loadTestsFromModule(module, use_load_tests):
if use_load_tests:
raise self.failureException('use_load_tests should be False for packages')
return module.path + ' module tests'
loader.loadTestsFromModule = loadTestsFromModule
loader._top_level_dir = '/foo'
# this time no '.py' on the pattern so that it can match
# a test package
suite = list(loader._find_tests('/foo', 'test*'))
# We should have loaded tests from the test_directory package by calling load_tests
# and directly from the test_directory2 package
self.assertEqual(suite, ['load_tests', '/foo/test_directory2 module tests'])
self.assertEqual(Module.paths, [os.path.join('/foo', 'test_directory'),
os.path.join('/foo', 'test_directory2')])
# load_tests should have been called once with loader, tests and pattern
self.assertEqual(Module.load_tests_args,
[(loader, os.path.join('/foo', 'test_directory') + ' module tests',
'test*')])
def test_discover(self):
loader = unittest.TestLoader()
original_isfile = os.path.isfile
def restore_isfile():
os.path.isfile = original_isfile
os.path.isfile = lambda path: False
self.addCleanup(restore_isfile)
full_path = os.path.abspath(os.path.normpath('/foo'))
def clean_path():
if sys.path[-1] == full_path:
sys.path.pop(-1)
self.addCleanup(clean_path)
with self.assertRaises(ImportError):
loader.discover('/foo/bar', top_level_dir='/foo')
self.assertEqual(loader._top_level_dir, full_path)
self.assertIn(full_path, sys.path)
os.path.isfile = lambda path: True
_find_tests_args = []
def _find_tests(start_dir, pattern):
_find_tests_args.append((start_dir, pattern))
return ['tests']
loader._find_tests = _find_tests
loader.suiteClass = str
suite = loader.discover('/foo/bar/baz', 'pattern', '/foo/bar')
top_level_dir = os.path.abspath(os.path.normpath('/foo/bar'))
start_dir = os.path.abspath(os.path.normpath('/foo/bar/baz'))
self.assertEqual(suite, "
[
'tests'
]
")
self.assertEqual(loader._top_level_dir, top_level_dir)
self.assertEqual(_find_tests_args, [(start_dir, 'pattern')])
def test_command_line_handling_parseArgs(self):
# Haha - take that uninstantiable class
program = object.__new__(TestProgram)
args = []
def do_discovery(argv):
args.extend(argv)
program._do_discovery = do_discovery
program.parseArgs(['something', 'discover'])
self.assertEqual(args, [])
program.parseArgs(['something', 'discover', 'foo', 'bar'])
self.assertEqual(args, ['foo', 'bar'])
def test_command_line_handling_do_discovery_too_many_arguments(self):
class Stop(Exception):
pass
def usageExit():
raise Stop
program = object.__new__(TestProgram)
program.usageExit = usageExit
with self.assertRaises(Stop):
# too many args
program._do_discovery(['one', 'two', 'three', 'four'])
def test_command_line_handling_do_discovery_calls_loader(self):
program = object.__new__(TestProgram)
class Loader(object):
args = []
def discover(self, start_dir, pattern, top_level_dir):
self.args.append((start_dir, pattern, top_level_dir))
return 'tests'
program._do_discovery(['-v'], Loader=Loader)
self.assertEqual(program.verbosity, 2)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('.', 'test*.py', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['--verbose'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('.', 'test*.py', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery([], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('.', 'test*.py', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['fish'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('fish', 'test*.py', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['fish', 'eggs'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('fish', 'eggs', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['fish', 'eggs', 'ham'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('fish', 'eggs', 'ham')])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['-s', 'fish'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('fish', 'test*.py', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['-t', 'fish'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('.', 'test*.py', 'fish')])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['-p', 'fish'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('.', 'fish', None)])
Loader.args = []
program = object.__new__(TestProgram)
program._do_discovery(['-p', 'eggs', '-s', 'fish', '-v'], Loader=Loader)
self.assertEqual(program.test, 'tests')
self.assertEqual(Loader.args, [('fish', 'eggs', None)])
self.assertEqual(program.verbosity, 2)
######################################################################
## Main
######################################################################
...
...
@@ -3387,7 +3682,7 @@ def test_main():
test_support.run_unittest(Test_TestCase, Test_TestLoader,
Test_TestSuite, Test_TestResult, Test_FunctionTestCase,
Test_TestSkipping, Test_Assertions, TestLongMessage,
Test_TestProgram, TestCleanUp)
Test_TestProgram, TestCleanUp
, TestDiscovery
)
if __name__ == "
__main__
":
test_main()
Lib/unittest.py
View file @
b4a81c83
...
...
@@ -56,6 +56,9 @@ import traceback
import
types
import
warnings
from
fnmatch
import
fnmatch
##############################################################################
# Exported classes and functions
##############################################################################
...
...
@@ -1196,6 +1199,7 @@ class TestLoader(object):
testMethodPrefix
=
'test'
sortTestMethodsUsing
=
cmp
suiteClass
=
TestSuite
_top_level_dir
=
None
def
loadTestsFromTestCase
(
self
,
testCaseClass
):
"""Return a suite of all tests cases contained in testCaseClass"""
...
...
@@ -1208,13 +1212,17 @@ class TestLoader(object):
suite
=
self
.
suiteClass
(
map
(
testCaseClass
,
testCaseNames
))
return
suite
def
loadTestsFromModule
(
self
,
module
):
def
loadTestsFromModule
(
self
,
module
,
use_load_tests
=
True
):
"""Return a suite of all tests cases contained in the given module"""
tests
=
[]
for
name
in
dir
(
module
):
obj
=
getattr
(
module
,
name
)
if
isinstance
(
obj
,
type
)
and
issubclass
(
obj
,
TestCase
):
tests
.
append
(
self
.
loadTestsFromTestCase
(
obj
))
load_tests
=
getattr
(
module
,
'load_tests'
,
None
)
if
use_load_tests
and
load_tests
is
not
None
:
return
load_tests
(
self
,
tests
,
None
)
return
self
.
suiteClass
(
tests
)
def
loadTestsFromName
(
self
,
name
,
module
=
None
):
...
...
@@ -1283,7 +1291,97 @@ class TestLoader(object):
testFnNames
.
sort
(
key
=
_CmpToKey
(
self
.
sortTestMethodsUsing
))
return
testFnNames
def
discover
(
self
,
start_dir
,
pattern
=
'test*.py'
,
top_level_dir
=
None
):
"""Find and return all test modules from the specified start
directory, recursing into subdirectories to find them. Only test files
that match the pattern will be loaded. (Using shell style pattern
matching.)
All test modules must be importable from the top level of the project.
If the start directory is not the top level directory then the top
level directory must be specified separately.
If a test package name (directory with '__init__.py') matches the
pattern then the package will be checked for a 'load_tests' function. If
this exists then it will be called with loader, tests, pattern.
If load_tests exists then discovery does *not* recurse into the package,
load_tests is responsible for loading all tests in the package.
The pattern is deliberately not stored as a loader attribute so that
packages can continue discovery themselves. top_level_dir is stored so
load_tests does not need to pass this argument in to loader.discover().
"""
if
top_level_dir
is
None
and
self
.
_top_level_dir
is
not
None
:
# make top_level_dir optional if called from load_tests in a package
top_level_dir
=
self
.
_top_level_dir
elif
top_level_dir
is
None
:
top_level_dir
=
start_dir
top_level_dir
=
os
.
path
.
abspath
(
os
.
path
.
normpath
(
top_level_dir
))
start_dir
=
os
.
path
.
abspath
(
os
.
path
.
normpath
(
start_dir
))
if
not
top_level_dir
in
sys
.
path
:
# all test modules must be importable from the top level directory
sys
.
path
.
append
(
top_level_dir
)
self
.
_top_level_dir
=
top_level_dir
if
start_dir
!=
top_level_dir
and
not
os
.
path
.
isfile
(
os
.
path
.
join
(
start_dir
,
'__init__.py'
)):
# what about __init__.pyc or pyo (etc)
raise
ImportError
(
'Start directory is not importable: %r'
%
start_dir
)
tests
=
list
(
self
.
_find_tests
(
start_dir
,
pattern
))
return
self
.
suiteClass
(
tests
)
def
_get_module_from_path
(
self
,
path
):
"""Load a module from a path relative to the top-level directory
of a project. Used by discovery."""
path
=
os
.
path
.
splitext
(
os
.
path
.
normpath
(
path
))[
0
]
relpath
=
os
.
path
.
relpath
(
path
,
self
.
_top_level_dir
)
assert
not
os
.
path
.
isabs
(
relpath
),
"Path must be within the project"
assert
not
relpath
.
startswith
(
'..'
),
"Path must be within the project"
name
=
relpath
.
replace
(
os
.
path
.
sep
,
'.'
)
__import__
(
name
)
return
sys
.
modules
[
name
]
def
_find_tests
(
self
,
start_dir
,
pattern
):
"""Used by discovery. Yields test suites it loads."""
paths
=
os
.
listdir
(
start_dir
)
for
path
in
paths
:
full_path
=
os
.
path
.
join
(
start_dir
,
path
)
# what about __init__.pyc or pyo (etc)
# we would need to avoid loading the same tests multiple times
# from '.py', '.pyc' *and* '.pyo'
if
os
.
path
.
isfile
(
full_path
)
and
path
.
lower
().
endswith
(
'.py'
):
if
fnmatch
(
path
,
pattern
):
# if the test file matches, load it
module
=
self
.
_get_module_from_path
(
full_path
)
yield
self
.
loadTestsFromModule
(
module
)
elif
os
.
path
.
isdir
(
full_path
):
if
not
os
.
path
.
isfile
(
os
.
path
.
join
(
full_path
,
'__init__.py'
)):
continue
load_tests
=
None
tests
=
None
if
fnmatch
(
path
,
pattern
):
# only check load_tests if the package directory itself matches the filter
package
=
self
.
_get_module_from_path
(
full_path
)
load_tests
=
getattr
(
package
,
'load_tests'
,
None
)
tests
=
self
.
loadTestsFromModule
(
package
,
use_load_tests
=
False
)
if
load_tests
is
None
:
if
tests
is
not
None
:
# tests loaded from package file
yield
tests
# recurse into the package
for
test
in
self
.
_find_tests
(
full_path
,
pattern
):
yield
test
else
:
yield
load_tests
(
self
,
tests
,
pattern
)
defaultTestLoader
=
TestLoader
()
...
...
@@ -1484,11 +1582,37 @@ class TextTestRunner(object):
# Facilities for running tests from the command line
##############################################################################
class
TestProgram
(
object
):
"""A command-line program that runs a set of tests; this is primarily
for making test modules conveniently executable.
"""
USAGE
=
"""
\
USAGE_AS_MAIN
=
"""
\
Usage: %(progName)s [options] [tests]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
Examples:
%(progName)s test_module - run tests from test_module
%(progName)s test_module.TestClass - run tests from
test_module.TestClass
%(progName)s test_module.TestClass.test_method - run specified test method
[tests] can be a list of any number of test modules, classes and test
methods.
Alternative Usage: %(progName)s discover [options]
Options:
-v, --verbose Verbose output
-s directory Directory to start discovery ('.' default)
-p pattern Pattern to match test files ('test*.py' default)
-t directory Top level directory of project (default to
start directory)
For test discovery all test modules must be importable from the top
level directory of the project.
"""
USAGE_FROM_MODULE
=
"""
\
Usage: %(progName)s [options] [test] [...]
Options:
...
...
@@ -1503,6 +1627,18 @@ Examples:
%(progName)s MyTestCase - run all 'test*' test methods
in MyTestCase
"""
if
__name__
==
'__main__'
:
USAGE
=
USAGE_AS_MAIN
else
:
USAGE
=
USAGE_FROM_MODULE
class
TestProgram
(
object
):
"""A command-line program that runs a set of tests; this is primarily
for making test modules conveniently executable.
"""
USAGE
=
USAGE
def
__init__
(
self
,
module
=
'__main__'
,
defaultTest
=
None
,
argv
=
None
,
testRunner
=
TextTestRunner
,
testLoader
=
defaultTestLoader
,
exit
=
True
,
...
...
@@ -1532,6 +1668,10 @@ Examples:
sys
.
exit
(
2
)
def
parseArgs
(
self
,
argv
):
if
len
(
argv
)
>
1
and
argv
[
1
].
lower
()
==
'discover'
:
self
.
_do_discovery
(
argv
[
2
:])
return
import
getopt
long_opts
=
[
'help'
,
'verbose'
,
'quiet'
]
try
:
...
...
@@ -1548,7 +1688,8 @@ Examples:
return
if
len
(
args
)
>
0
:
self
.
testNames
=
args
if
sys
.
modules
[
'unittest'
]
is
sys
.
modules
[
'__main__'
]:
if
__name__
==
'__main__'
:
# to support python -m unittest ...
self
.
module
=
None
else
:
self
.
testNames
=
(
self
.
defaultTest
,)
...
...
@@ -1560,6 +1701,36 @@ Examples:
self
.
test
=
self
.
testLoader
.
loadTestsFromNames
(
self
.
testNames
,
self
.
module
)
def
_do_discovery
(
self
,
argv
,
Loader
=
TestLoader
):
# handle command line args for test discovery
import
optparse
parser
=
optparse
.
OptionParser
()
parser
.
add_option
(
'-v'
,
'--verbose'
,
dest
=
'verbose'
,
default
=
False
,
help
=
'Verbose output'
,
action
=
'store_true'
)
parser
.
add_option
(
'-s'
,
'--start-directory'
,
dest
=
'start'
,
default
=
'.'
,
help
=
"Directory to start discovery ('.' default)"
)
parser
.
add_option
(
'-p'
,
'--pattern'
,
dest
=
'pattern'
,
default
=
'test*.py'
,
help
=
"Pattern to match tests ('test*.py' default)"
)
parser
.
add_option
(
'-t'
,
'--top-level-directory'
,
dest
=
'top'
,
default
=
None
,
help
=
'Top level directory of project (defaults to start directory)'
)
options
,
args
=
parser
.
parse_args
(
argv
)
if
len
(
args
)
>
3
:
self
.
usageExit
()
for
name
,
value
in
zip
((
'start'
,
'pattern'
,
'top'
),
args
):
setattr
(
options
,
name
,
value
)
if
options
.
verbose
:
self
.
verbosity
=
2
start_dir
=
options
.
start
pattern
=
options
.
pattern
top_level_dir
=
options
.
top
loader
=
Loader
()
self
.
test
=
loader
.
discover
(
start_dir
,
pattern
,
top_level_dir
)
def
runTests
(
self
):
if
isinstance
(
self
.
testRunner
,
(
type
,
types
.
ClassType
)):
try
:
...
...
Misc/NEWS
View file @
b4a81c83
...
...
@@ -503,6 +503,9 @@ Library
- unittest.assertNotEqual() now uses the inequality operator (!=) instead
of the equality operator.
- Issue #6001: Test discovery for unittest. Implemented in
unittest.TestLoader.discover and from the command line.
- Issue #5679: The methods unittest.TestCase.addCleanup and doCleanups were added.
addCleanup allows you to add cleanup functions that will be called
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment