ChatGPT解决这个技术问题 Extra ChatGPT

Running a single test from unittest.TestCase via the command line

In our team, we define most test cases like this:

One "framework" class ourtcfw.py:

import unittest

class OurTcFw(unittest.TestCase):
    def setUp:
        # Something

    # Other stuff that we want to use everywhere

And a lot of test cases like testMyCase.py:

import localweather

class MyCase(OurTcFw):

    def testItIsSunny(self):
        self.assertTrue(localweather.sunny)

    def testItIsHot(self):
        self.assertTrue(localweather.temperature > 20)

if __name__ == "__main__":
    unittest.main()

When I'm writing new test code and want to run it often, and save time, I do put "__" in front of all other tests. But it's cumbersome, distracts me from the code I'm writing, and the commit noise this creates is plain annoying.

So, for example, when making changes to testItIsHot(), I want to be able to do this:

$ python testMyCase.py testItIsHot

and have unittest run only testItIsHot()

How can I achieve that?

I tried to rewrite the if __name__ == "__main__": part, but since I'm new to Python, I'm feeling lost and keep bashing into everything else than the methods.


p
phihag

This works as you suggest - you just have to specify the class name as well:

python testMyCase.py MyCase.testItIsHot

Oh my! Since the tests are to be run on python2.6 (99% of the time I can test the tests themselves with python2.7), I was looking at 2.6.8 doc and missed so much! :-)
Just noticed that this works only if the method is called "test*", so unfortunately it cannot be used to occasionally run test that is "disabled" by rename
Doesn't work for tests in a subdirectory - the most common case in a mature Python program.
@TomSwirly Can't check now but I think you can do it by creatiing (empty) __init__.py inside that direcrory (and subdirs, if any) and calling eg. python test/testMyCase.py test.MyCase.testItIsHot.
Nothing happens when I do this. I found workarounds, but I was hoping this method would work for me.
P
Peter Mortensen

If you organize your test cases, that is, follow the same organization like the actual code and also use relative imports for modules in the same package, you can also use the following command format:

python -m unittest mypkg.tests.test_module.TestClass.test_method

# In your case, this would be:
python -m unittest testMyCase.MyCase.testItIsHot

Python 3 documentation for this: Command-Line Interface


This is so clunkily Java-esque. "long_module_name.SameLongNameAsAClass.test_long_name_beginning_with_test_as_a_convention" ...better hope you didn't modularize into suites like a sane person who tests their code.
C
Community

It can work well as you guess

python testMyCase.py MyCase.testItIsHot

And there is another way to just test testItIsHot:

    suite = unittest.TestSuite()
    suite.addTest(MyCase("testItIsHot"))
    runner = unittest.TextTestRunner()
    runner.run(suite)

I found the second part of this answer extremely helpful: I am writing tests in Eclipse + PyDev and I don't want to switch to the command line!
To complement this answer, in case you want to run the full TestCase class you can omit the suite part and do instead: runner = unittest.TextTestRunner() followed by runner.run(unittest.makeSuite(MyCase))
P
Peter Mortensen

If you check out the help of the unittest module it tells you about several combinations that allow you to run test case classes from a module and test methods from a test case class.

python3 -m unittest -h

[...]

Examples:
  python3 -m unittest test_module               - run tests from test_module
  python3 -m unittest module.TestClass          - run tests from module.TestClass
  python3 -m unittest module.Class.test_method  - run specified test method
```lang-none

It does not require you to define a `unittest.main()` as the default behaviour of your module.


+1 and since terminology can be confusing if new to a language (and the usage is even oddly inconsistent): running python -m unittest module_test.TestClass.test_method assumes a file module_test.py (run from current directory; and __init.py__ is not required); and module_test.py contains class TestClass(unittest.TestCase)... which contains def test_method(self,...) (this also works for me on python 2.7.13)
P
Peter Mortensen

In case you want to run only tests from a specific class:

if __name__ == "__main__":
    unittest.main(MyCase())

It works for me in Python 3.6.


P
Peter Mortensen

TL;DR: This would very likely work:

python mypkg/tests/test_module.py MyCase.testItIsHot

The explanation:

The convenient way python mypkg/tests/test_module.py MyCase.testItIsHot would work, but its unspoken assumption is you already have this conventional code snippet inside (typically at the end of) your test file. if __name__ == "__main__": unittest.main()

The inconvenient way python -m unittest mypkg.tests.test_module.TestClass.test_method would always work, without requiring you to have that if __name__ == "__main__": unittest.main() code snippet in your test source file.

So why is the second method considered inconvenient? Because it would be a pain in the <insert one of your body parts here> to type that long, dot-delimited path by hand. While in the first method, the mypkg/tests/test_module.py part can be auto-completed, either by a modern shell, or by your editor.


P
Peter Mortensen

Inspired by yarkee, I combined it with some of the code I already got. You can also call this from another script, just by calling the function run_unit_tests() without requiring to use the command line, or just call it from the command line with python3 my_test_file.py.

import my_test_file
my_test_file.run_unit_tests()

Sadly this only works for Python 3.3 or above:

import unittest

class LineBalancingUnitTests(unittest.TestCase):

    @classmethod
    def setUp(self):
        self.maxDiff = None

    def test_it_is_sunny(self):
        self.assertTrue("a" == "a")

    def test_it_is_hot(self):
        self.assertTrue("a" != "b")

Runner code:

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import unittest
from .somewhere import LineBalancingUnitTests

def create_suite(classes, unit_tests_to_run):
    suite = unittest.TestSuite()
    unit_tests_to_run_count = len( unit_tests_to_run )

    for _class in classes:
        _object = _class()
        for function_name in dir( _object ):
            if function_name.lower().startswith( "test" ):
                if unit_tests_to_run_count > 0 \
                        and function_name not in unit_tests_to_run:
                    continue
                suite.addTest( _class( function_name ) )
    return suite

def run_unit_tests():
    runner = unittest.TextTestRunner()
    classes =  [
        LineBalancingUnitTests,
    ]

    # Comment all the tests names on this list, to run all Unit Tests
    unit_tests_to_run =  [
        "test_it_is_sunny",
        # "test_it_is_hot",
    ]
    runner.run( create_suite( classes, unit_tests_to_run ) )

if __name__ == "__main__":
    print( "\n\n" )
    run_unit_tests()

Editing the code a little, you can pass an array with all unit tests you would like to call:

...
def run_unit_tests(unit_tests_to_run):
    runner = unittest.TextTestRunner()

    classes = \
    [
        LineBalancingUnitTests,
    ]

    runner.run( suite( classes, unit_tests_to_run ) )
...

And another file:

import my_test_file

# Comment all the tests names on this list, to run all unit tests
unit_tests_to_run = \
[
    "test_it_is_sunny",
    # "test_it_is_hot",
]

my_test_file.run_unit_tests( unit_tests_to_run )

Alternatively, you can use load_tests Protocol and define the following method in your test module/file:

def load_tests(loader, standard_tests, pattern):
    suite = unittest.TestSuite()

    # To add a single test from this file
    suite.addTest( LineBalancingUnitTests( 'test_it_is_sunny' ) )

    # To add a single test class from this file
    suite.addTests( unittest.TestLoader().loadTestsFromTestCase( LineBalancingUnitTests ) )

    return suite

If you want to limit the execution to one single test file, you just need to set the test discovery pattern to the only file where you defined the load_tests() function.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import unittest

test_pattern = 'mytest/module/name.py'
PACKAGE_ROOT_DIRECTORY = os.path.dirname( os.path.realpath( __file__ ) )

loader = unittest.TestLoader()
start_dir = os.path.join( PACKAGE_ROOT_DIRECTORY, 'testing' )

suite = loader.discover( start_dir, test_pattern )
runner = unittest.TextTestRunner( verbosity=2 )
results = runner.run( suite )

print( "results: %s" % results )
print( "results.wasSuccessful: %s" % results.wasSuccessful() )

sys.exit( not results.wasSuccessful() )

References:

Problem with sys.argv[1] when unittest module is in a script Is there a way to loop through and execute all of the functions in a Python class? looping over all member variables of a class in python

Alternatively, to the last main program example, I came up with the following variation after reading the unittest.main() method implementation:

https://github.com/python/cpython/blob/master/Lib/unittest/main.py#L65

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import unittest

PACKAGE_ROOT_DIRECTORY = os.path.dirname( os.path.realpath( __file__ ) )
start_dir = os.path.join( PACKAGE_ROOT_DIRECTORY, 'testing' )

from testing_package import main_unit_tests_module
testNames = ["TestCaseClassName.test_nameHelloWorld"]

loader = unittest.TestLoader()
suite = loader.loadTestsFromNames( testNames, main_unit_tests_module )

runner = unittest.TextTestRunner(verbosity=2)
results = runner.run( suite )

print( "results: %s" % results )
print( "results.wasSuccessful: %s" % results.wasSuccessful() )
sys.exit( not results.wasSuccessful() )

r
ronkov

If you want to run the test directly from a script (for example, from a jupyter notebook), you can do this to run just one test:

from testMyCase import MyCase
unittest.main(argv=['ignored', '-v', 'MyCase.testItIsHot'], exit=False)

R
Rodolfo Ortega

What worked for me was:

cd project_dir
python -m unittest -v path\to\test\testMyCase.py -k my_test_name

-v is for unittest verbose log output.