Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 37d120a

Browse files
author
Michael Foord
committed
Issue 10620: Specifying test modules by path instead of module name to 'python -m unittest'
1 parent e2bb4eb commit 37d120a

4 files changed

Lines changed: 114 additions & 5 deletions

File tree

Doc/library/unittest.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,16 @@ modules, classes or even individual test methods::
204204
You can pass in a list with any combination of module names, and fully
205205
qualified class or method names.
206206

207+
Test modules can be specified by file path as well::
208+
209+
python -m unittest tests/test_something.py
210+
211+
This allows you to use the shell filename completion to specify the test module.
212+
The file specified must still be importable as a module. The path is converted
213+
to a module name by removing the '.py' and converting path separators into '.'.
214+
If you want to execute a test file that isn't importable as a module you should
215+
execute the file directly instead.
216+
207217
You can run tests with more detail (higher verbosity) by passing in the -v flag::
208218

209219
python -m unittest -v test_module

Lib/unittest/main.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,24 @@
5858
in MyTestCase
5959
"""
6060

61+
def _convert_name(name):
62+
# on Linux / Mac OS X 'foo.PY' is not importable, but on
63+
# Windows it is. Simpler to do a case insensitive match
64+
# a better check would be to check that the name is a
65+
# valid Python module name.
66+
if os.path.isfile(name) and name.lower().endswith('.py'):
67+
if os.path.isabs(name):
68+
rel_path = os.path.relpath(name, os.getcwd())
69+
if os.path.isabs(rel_path) or rel_path.startswith(os.pardir):
70+
return name
71+
name = rel_path
72+
# on Windows both '\' and '/' are used as path
73+
# separators. Better to replace both than rely on os.path.sep
74+
return name[:-3].replace('\\', '.').replace('/', '.')
75+
return name
6176

77+
def _convert_names(names):
78+
return [_convert_name(name) for name in names]
6279

6380
class TestProgram(object):
6481
"""A command-line program that runs a set of tests; this is primarily
@@ -153,7 +170,7 @@ def parseArgs(self, argv):
153170
# createTests will load tests from self.module
154171
self.testNames = None
155172
elif len(args) > 0:
156-
self.testNames = args
173+
self.testNames = _convert_names(args)
157174
if __name__ == '__main__':
158175
# to support python -m unittest ...
159176
self.module = None

Lib/unittest/test/test_program.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,85 @@ def fakeInstallHandler():
273273
program.runTests()
274274
self.assertTrue(self.installed)
275275

276+
def _patch_isfile(self, names, exists=True):
277+
def isfile(path):
278+
return path in names
279+
original = os.path.isfile
280+
os.path.isfile = isfile
281+
def restore():
282+
os.path.isfile = original
283+
self.addCleanup(restore)
284+
285+
286+
def testParseArgsFileNames(self):
287+
# running tests with filenames instead of module names
288+
program = self.program
289+
argv = ['progname', 'foo.py', 'bar.Py', 'baz.PY', 'wing.txt']
290+
self._patch_isfile(argv)
291+
292+
program.createTests = lambda: None
293+
program.parseArgs(argv)
294+
295+
# note that 'wing.txt' is not a Python file so the name should
296+
# *not* be converted to a module name
297+
expected = ['foo', 'bar', 'baz', 'wing.txt']
298+
self.assertEqual(program.testNames, expected)
299+
300+
301+
def testParseArgsFilePaths(self):
302+
program = self.program
303+
argv = ['progname', 'foo/bar/baz.py', 'green\\red.py']
304+
self._patch_isfile(argv)
305+
306+
program.createTests = lambda: None
307+
program.parseArgs(argv)
308+
309+
expected = ['foo.bar.baz', 'green.red']
310+
self.assertEqual(program.testNames, expected)
311+
312+
313+
def testParseArgsNonExistentFiles(self):
314+
program = self.program
315+
argv = ['progname', 'foo/bar/baz.py', 'green\\red.py']
316+
self._patch_isfile([])
317+
318+
program.createTests = lambda: None
319+
program.parseArgs(argv)
320+
321+
self.assertEqual(program.testNames, argv[1:])
322+
323+
def testParseArgsAbsolutePathsThatCanBeConverted(self):
324+
cur_dir = os.getcwd()
325+
program = self.program
326+
def _join(name):
327+
return os.path.join(cur_dir, name)
328+
argv = ['progname', _join('foo/bar/baz.py'), _join('green\\red.py')]
329+
self._patch_isfile(argv)
330+
331+
program.createTests = lambda: None
332+
program.parseArgs(argv)
333+
334+
expected = ['foo.bar.baz', 'green.red']
335+
self.assertEqual(program.testNames, expected)
336+
337+
def testParseArgsAbsolutePathsThatCannotBeConverted(self):
338+
program = self.program
339+
# will this test work on Windows? (is '/...' considered absolute?)
340+
argv = ['progname', '/foo/bar/baz.py', '/green/red.py']
341+
self._patch_isfile(argv)
342+
343+
program.createTests = lambda: None
344+
program.parseArgs(argv)
345+
346+
self.assertEqual(program.testNames, argv[1:])
347+
348+
# it may be better to use platform specific functions to normalise paths
349+
# rather than accepting '.PY' and '\' as file seprator on Linux / Mac
350+
# it would also be better to check that a filename is a valid module
351+
# identifier (we have a regex for this in loader.py)
352+
# for invalid filenames should we raise a useful error rather than
353+
# leaving the current error message (import of filename fails) in place?
354+
276355

277356
if __name__ == '__main__':
278357
unittest.main()

Misc/NEWS

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ Core and Builtins
3737
Library
3838
-------
3939

40+
- Issue 10620: `python -m unittest` can accept file paths instead of module
41+
names for running specific tests.
42+
43+
- Issue #9424: Deprecate the `unittest.TestCase` methods `assertEquals`,
44+
`assertNotEquals`, `assertAlmostEquals`, `assertNotAlmostEquals` and `assert_`
45+
and replace them with the correct methods in the Python test suite.
46+
4047
- Issue #10272: The ssl module now raises socket.timeout instead of a generic
4148
SSLError on socket timeouts.
4249

@@ -236,10 +243,6 @@ Tests
236243
- `python -m test` can be used to run the test suite as well as
237244
`python -m test.regrtest`.
238245

239-
- Issue #9424: Deprecate the `unittest.TestCase` methods `assertEquals`,
240-
`assertNotEquals`, `assertAlmostEquals`, `assertNotAlmostEquals` and `assert_`
241-
and replace them with the correct methods in the Python test suite.
242-
243246
- Do not fail test_socket when the IP address of the local hostname cannot be
244247
looked up.
245248

0 commit comments

Comments
 (0)