标签归档:python-unittest

暂时禁用单个Python单元测试

问题:暂时禁用单个Python单元测试

unittest在Python中使用模块时,如何暂时禁用各个单元测试?

How can individual unit tests be temporarily disabled when using the unittest module in Python?


回答 0

单个测试方法或类都可以使用unittest.skip装饰器禁用。

@unittest.skip("reason for skipping")
def test_foo():
    print('This is foo test case.')


@unittest.skip  # no reason needed
def test_bar():
    print('This is bar test case.')

有关其他选项,请参阅文档“ 跳过测试和预期的失败”

Individual test methods or classes can both be disabled using the unittest.skip decorator.

@unittest.skip("reason for skipping")
def test_foo():
    print('This is foo test case.')


@unittest.skip  # no reason needed
def test_bar():
    print('This is bar test case.')

For other options, see the docs for Skipping tests and expected failures.


回答 1

您可以使用装饰器禁用可以包装功能的测试,并阻止googletest或python单元测试运行测试用例。

def disabled(f):
    def _decorator():
        print f.__name__ + ' has been disabled'
    return _decorator

@disabled
def testFoo():
    '''Foo test case'''
    print 'this is foo test case'

testFoo()

输出:

testFoo has been disabled

You can use decorators to disable the test that can wrap the function and prevent the googletest or python unit test to run the testcase.

def disabled(f):
    def _decorator():
        print f.__name__ + ' has been disabled'
    return _decorator

@disabled
def testFoo():
    '''Foo test case'''
    print 'this is foo test case'

testFoo()

Output:

testFoo has been disabled

回答 2

最新版本(2.7版)支持测试跳过/禁用,就像这样。您可以仅获取此模块并将其用于现有的Python安装中。它可能会工作。

在此之前,我曾经重命名过xtest_testname要从中跳过的测试test_testname


这是一个快速的elisp脚本来执行此操作。我的elisp有点生锈,因此对于出现的任何问题我都表示歉意。未经测试。

  (defun disable_enable_test ()
  (interactive "")
  (save-excursion
    (beginning-of-line)
    (search-forward "def")
    (forward-char)
    (if (looking-at "disable_")
    (zap-to-char 1 ?_)
      (insert "disable_"))))

The latest version (2.7 – unreleased) supports test skipping/disabling like so. You could just get this module and use it on your existing Python install. It will probably work.

Before this, I used to rename the tests I wanted skipped to xtest_testname from test_testname.


Here’s a quick elisp script to do this. My elisp is a little rusty so I apologise in advance for any problems it has. Untested.

  (defun disable_enable_test ()
  (interactive "")
  (save-excursion
    (beginning-of-line)
    (search-forward "def")
    (forward-char)
    (if (looking-at "disable_")
    (zap-to-char 1 ?_)
      (insert "disable_"))))

回答 3

只需将@unittest.SkipTest装饰器放在测试之上就足够了。

Simply placing @unittest.SkipTest decorator above the test is enough.


回答 4

我只是用下划线将测试用例方法重命名:test_myfunc变成_test_myfunc。

I just rename a test case method with an underscore: test_myfunc becomes _test_myfunc.


回答 5

2.1 的文档未指定忽略或跳过方法。

不过通常,我会在需要时阻止评论。

The docs for 2.1 don’t specify an ignore or skip method.

Usually though, I block comment when needed.


回答 6

专注于问题的“暂时禁用”部分,最佳答案在某种程度上取决于用例。这里带给我的用例是我正在对功能进行测试驱动的开发。在此过程中,我连续编写测试,并经常在函数中使用断点进行调试。如果我每次运行测试运行程序时都运行所有测试,那么最终我会在已经生效的测试的断点处停止。我不需要添加“跳过”或修改测试名称或类似名称,因为当我编写完函数后,我希望所有测试都可以运行。如果使用“跳过”,则必须返回并“跳过”。

对于我的用例,解决方案在于测试运行器,而不是测试代码。我用pytest。使用pytest,可以从命令行轻松指定一个测试:

pytest PYTHON_FILENAME.TEST_CLASS.TEST_NAME

(将大写替换为您的值)。

我了解该问题是针对python-unitest的。我已经很久没有使用它了。如果它与pytest类似,我不会感到惊讶。如果没有,您可以轻松切换到pytest。您无需修改​​代码。只需安装它并更改您的测试运行器命令。

另外,我使用PyCharm Pro。在显示我的测试代码的页面上,每个测试的def旁边都有一个小图标。我可以单击该图标并单独运行该测试。

Focusing on the “temporarily disabled” part of the question, the best answer somewhat depends on the use case. The use case that brought me here is I am doing test driven development on a function. In this process, I successively write tests and often use break points in the function for debugging. If I just run all the tests every time I run the test runner, I end up stopping at break points for tests that already work. Adding “skip” or munging the test name or something like that is not what I want because when I am done writing the function, I want all tests to run. If I used “skip” I would have to go back and “unskip”.

For my use case, the solution lies in the test runner, not in the test code. I use pytest. With pytest, it is easy to specify a single test from the command line:

pytest PYTHON_FILENAME.TEST_CLASS.TEST_NAME

(replace the caps with your values).

I understand the that question was for python-unitest. I have not used that in a long time. I would not be surprised if it had something similar to pytest. If not, you can easily switch to pytest. You do not need to modify your code. Just install it and change your test runner command.

Also, I use PyCharm Pro. On the page that shows my test code, there is a small icon next to the def for each test. I can click that icon and run that test individually.


Python从导入的模块中模拟函数

问题:Python从导入的模块中模拟函数

我想了解如何@patch从导入的模块执行功能。

这是我到目前为止的位置。

app / mocking.py:

from app.my_module import get_user_name

def test_method():
  return get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()

app / my_module / __ init__.py:

def get_user_name():
  return "Unmocked User"

测试/模拟测试.py:

import unittest
from app.mocking import test_method 

def mock_get_user():
  return "Mocked This Silly"

@patch('app.my_module.get_user_name')
class MockingTestTestCase(unittest.TestCase):

  def test_mock_stubs(self, mock_method):
    mock_method.return_value = 'Mocked This Silly')
    ret = test_method()
    self.assertEqual(ret, 'Mocked This Silly')

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

这不符合我的预期。“已修补”模块仅返回的未模拟值get_user_name。如何模拟要导入到被测命名空间中的其他包中的方法?

I want to understand how to @patch a function from an imported module.

This is where I am so far.

app/mocking.py:

from app.my_module import get_user_name

def test_method():
  return get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()

app/my_module/__init__.py:

def get_user_name():
  return "Unmocked User"

test/mock-test.py:

import unittest
from app.mocking import test_method 

def mock_get_user():
  return "Mocked This Silly"

@patch('app.my_module.get_user_name')
class MockingTestTestCase(unittest.TestCase):

  def test_mock_stubs(self, mock_method):
    mock_method.return_value = 'Mocked This Silly')
    ret = test_method()
    self.assertEqual(ret, 'Mocked This Silly')

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

This does not work as I would expect. The “patched” module simply returns the unmocked value of get_user_name. How do I mock methods from other packages that I am importing into a namespace under test?


回答 0

当您patchunittest.mock包中使用装饰器时,您未在修补命名空间,而是从(在这种情况下app.my_module.get_user_name)导入模块,而是在被测试的命名空间中对其进行修补app.mocking.get_user_name

为此,请Mock尝试以下类似方法:

from mock import patch
from app.mocking import test_method 

class MockingTestTestCase(unittest.TestCase):

    @patch('app.mocking.get_user_name')
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = 'Mocked This Silly'
        ret = test_method()
        self.assertEqual(ret, 'Mocked This Silly')

标准库文档中包含一个有用的部分对此进行了描述。

When you are using the patch decorator from the unittest.mock package you are not patching the namespace the module is imported from (in this case app.my_module.get_user_name) you are patching it in the namespace under test app.mocking.get_user_name.

To do the above with Mock try something like the below:

from mock import patch
from app.mocking import test_method 

class MockingTestTestCase(unittest.TestCase):

    @patch('app.mocking.get_user_name')
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = 'Mocked This Silly'
        ret = test_method()
        self.assertEqual(ret, 'Mocked This Silly')

The standard library documentation includes a useful section describing this.


回答 1

尽管Matti John的答案解决了您的问题(也为我提供了帮助,谢谢!),但是,我建议将本地的“ get_user_name”函数替换为模拟的函数。这将允许您控制何时替换功能以及何时不替换功能。同样,这将允许您在同一测试中进行多次替换。为此,请以类似的方式使用“ with”语句:

from mock import patch

class MockingTestTestCase(unittest.TestCase):

    def test_mock_stubs(self):
        with patch('app.mocking.get_user_name', return_value = 'Mocked This Silly'):
            ret = test_method()
            self.assertEqual(ret, 'Mocked This Silly')

While Matti John’s answer solves your issue (and helped me too, thanks!), I would, however, suggest localizing the replacement of the original ‘get_user_name’ function with the mocked one. This will allow you to control when the function is replaced and when it isn’t. Also, this will allow you to make several replacements in the same test. In order to do so, use the ‘with’ statment in a pretty simillar manner:

from mock import patch

class MockingTestTestCase(unittest.TestCase):

    def test_mock_stubs(self):
        with patch('app.mocking.get_user_name', return_value = 'Mocked This Silly'):
            ret = test_method()
            self.assertEqual(ret, 'Mocked This Silly')

如何在单元测试中使用JSON发送请求

问题:如何在单元测试中使用JSON发送请求

我在Flask应用程序中包含在请求中使用JSON的代码,并且可以像这样获取JSON对象:

Request = request.get_json()

一切正常,但是我试图使用Python的unittest模块创建单元测试,并且很难找到一种发送带有请求的JSON的方法。

response=self.app.post('/test_function', 
                       data=json.dumps(dict(foo = 'bar')))

这给了我:

>>> request.get_data()
'{"foo": "bar"}'
>>> request.get_json()
None

Flask似乎有一个JSON参数,您可以在其中发布请求中设置json = dict(foo =’bar’),但我不知道如何使用unittest模块来做到这一点。

I have code within a Flask application that uses JSONs in the request, and I can get the JSON object like so:

Request = request.get_json()

This has been working fine, however I am trying to create unit tests using Python’s unittest module and I’m having difficulty finding a way to send a JSON with the request.

response=self.app.post('/test_function', 
                       data=json.dumps(dict(foo = 'bar')))

This gives me:

>>> request.get_data()
'{"foo": "bar"}'
>>> request.get_json()
None

Flask seems to have a JSON argument where you can set json=dict(foo=’bar’) within the post request, but I don’t know how to do that with the unittest module.


回答 0

将帖子更改为

response=self.app.post('/test_function', 
                       data=json.dumps(dict(foo='bar')),
                       content_type='application/json')

解决它。

感谢user3012759。

Changing the post to

response=self.app.post('/test_function', 
                       data=json.dumps(dict(foo='bar')),
                       content_type='application/json')

fixed it.

Thanks to user3012759.


回答 1

更新:由于Flask 1.0发布的flask.testing.FlaskClient方法接受json参数和Response.get_json添加的方法,请参见example

对于Flask 0.x,您可以使用以下收据:

from flask import Flask, Response as BaseResponse, json
from flask.testing import FlaskClient
from werkzeug.utils import cached_property


class Response(BaseResponse):
    @cached_property
    def json(self):
        return json.loads(self.data)


class TestClient(FlaskClient):
    def open(self, *args, **kwargs):
        if 'json' in kwargs:
            kwargs['data'] = json.dumps(kwargs.pop('json'))
            kwargs['content_type'] = 'application/json'
        return super(TestClient, self).open(*args, **kwargs)


app = Flask(__name__)
app.response_class = Response
app.test_client_class = TestClient
app.testing = True

UPDATE: Since Flask 1.0 released flask.testing.FlaskClient methods accepts json argument and Response.get_json method added, see example.

for Flask 0.x you may use receipt below:

from flask import Flask, Response as BaseResponse, json
from flask.testing import FlaskClient
from werkzeug.utils import cached_property


class Response(BaseResponse):
    @cached_property
    def json(self):
        return json.loads(self.data)


class TestClient(FlaskClient):
    def open(self, *args, **kwargs):
        if 'json' in kwargs:
            kwargs['data'] = json.dumps(kwargs.pop('json'))
            kwargs['content_type'] = 'application/json'
        return super(TestClient, self).open(*args, **kwargs)


app = Flask(__name__)
app.response_class = Response
app.test_client_class = TestClient
app.testing = True

AttributeError:“模块”对象没有属性“测试”

问题:AttributeError:“模块”对象没有属性“测试”

我正在运行以下命令:

python manage.py test project.apps.app1.tests

并导致此错误:

AttributeError:“模块”对象没有属性“测试”

下面是我的目录结构。我还已将app1添加到已安装的应用程序配置中。

Traceback (most recent call last):
    File "manage.py", line 10, in <module> execute_from_command_line(sys.argv)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 50, in run_from_argv
    super(Command, self).run_from_argv(argv)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 71, in execute
    super(Command, self).execute(*args, **options)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 88, in handle
    failures = test_runner.run_tests(test_labels)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/test/runner.py", line 146, in run_tests
    suite = self.build_suite(test_labels, extra_tests)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/test/runner.py", line 66, in build_suite
    tests = self.test_loader.loadTestsFromName(label)
    File "/usr/lib/python2.7/unittest/loader.py", line 100, in loadTestsFromName
    parent, obj = obj, getattr(obj, part)
    AttributeError: 'module' object has no attribute 'tests'

目录结构:

I’m running this command:

python manage.py test project.apps.app1.tests

and it causes this error:

AttributeError: ‘module’ object has no attribute ‘tests’

Below is my directory structure. I’ve also added app1 to my installed apps config.

Traceback (most recent call last):
    File "manage.py", line 10, in <module> execute_from_command_line(sys.argv)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 50, in run_from_argv
    super(Command, self).run_from_argv(argv)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 71, in execute
    super(Command, self).execute(*args, **options)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 88, in handle
    failures = test_runner.run_tests(test_labels)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/test/runner.py", line 146, in run_tests
    suite = self.build_suite(test_labels, extra_tests)
    File "/home/username/local/dev/local/lib/python2.7/site-packages/django/test/runner.py", line 66, in build_suite
    tests = self.test_loader.loadTestsFromName(label)
    File "/usr/lib/python2.7/unittest/loader.py", line 100, in loadTestsFromName
    parent, obj = obj, getattr(obj, part)
    AttributeError: 'module' object has no attribute 'tests'

Directory structure:


回答 0

我终于想出了解决另一个问题的方法。问题是我的测试找不到导入。

如果您的测试无法导入,您似乎会收到上述错误。这是有道理的,因为测试套件无法导入损坏的测试。至少我认为这是正在发生的事情,因为我在测试文件中修复了导入,并确定导入已开始起作用。

要验证您的测试用例,只需尝试在python控制台中导入测试用例文件即可。

例:

from project.apps.app1.tests import *

I finally figured it out working on another problem. The problem was that my test couldn’t find an import.

It looks like you get the above error if your test fails to import. This makes sense because the test suite can’t import a broken test. At least I think this is what is going on because I fixed the import within my test file and sure enough it started working.

To validate your test case just try import the test case file in python console.

Example:

from project.apps.app1.tests import *

回答 1

用:

./manage.py shell

其次是

import myapp.tests

查找导入错误的性质。

Use:

./manage.py shell

followed by

import myapp.tests

to find the nature of the import error.


回答 2

就我而言,我需要在文件夹中创建一个空的__init__.pyapp/tests

For my case, I need to create an empty __init__.py in my app/tests folder


回答 3

上面的Steve Bradshaw的示例可解决导入错误(感谢Steve)。

其他类型的错误(例如ValueError)也可能导致

AttributeError: 'module' object has no attribute 'tests'

看看这些错误是什么

./manage.py shell
from myapp.tests import SomeTestCase
t = SomeTestCase()

Steve Bradshaw’s example above works for import errors (thanks Steve).

Other type of errors (e.g. ValueError) may also cause

AttributeError: 'module' object has no attribute 'tests'

to see what these errors are

./manage.py shell
from myapp.tests import SomeTestCase
t = SomeTestCase()

回答 4

我和克里斯有同样的错误。我删除了一个旧模型,然后运行tests.py,但是另一个文件(views.py)仍在尝试导入已删除的模型。

当我取出现在已经过时的import语句时,问题解决了。

I had the same error as Chris. I had deleted an old model, then run tests.py, but another file (views.py) was still trying to import the deleted model.

When I took out the now-obsolete import statement, problem solved.


回答 5

确保脚本中使用的所有模块均未损坏。我的意思是在您的导入语句中检查拼写。

# invalid import
from app.model.notification import Notification
# valid import
from app.models.notification import Notification

您可以通过在djano的交互式控制台中执行import语句来测试您的模块。

$root@13faefes8: python manage.py shell
Type "help", "copyright", "credits" or "license" for more information (InteractiveConsole)
>>> from app.model.notification import Notification
Traceback (most recent call last): 
   File "<console>", line 1, in <module>
ImportError: No module named model.notification

Make sure that all modules that you are using in your script are not broken. By this I mean check spelling in your import statements.

# invalid import
from app.model.notification import Notification
# valid import
from app.models.notification import Notification

You can test yours modules by executing imports statements in djano’s interactive console.

$root@13faefes8: python manage.py shell
Type "help", "copyright", "credits" or "license" for more information (InteractiveConsole)
>>> from app.model.notification import Notification
Traceback (most recent call last): 
   File "<console>", line 1, in <module>
ImportError: No module named model.notification

回答 6

我通过修复循环导入引用解决了错误“ AttributeError:模块’utils’没有属性’name_of_my_function’”。我的文件manage.py和utils.py每个都有一个指向彼此的import语句。

I resolved the error “AttributeError: module ‘utils’ has no attribute ‘name_of_my_function’ ” by fixing a circular import reference. My files manage.py and utils.py each had an import statement pointing at each other.


回答 7

根据django文档,当运行测试时,测试实用程序的默认行为是在名称以test开头的任何文件中查找所有测试用例(即unittest.TestCase的子类),自动从中构建测试套件。这些测试用例,然后运行该套件。

所以试试这个: python manage.py test tests.py

According to django document When you run your tests, the default behavior of the test utility is to find all the test cases (that is, subclasses of unittest.TestCase) in any file whose name begins with test, automatically build a test suite out of those test cases, and run that suite.

so try this : python manage.py test tests.py


回答 8

遇到相同的错误,但检查了所有原因列表,没有解决我的问题。

最后弄清楚,原因是导入但尚未使用的一种方法的名称不正确。尽管这是一个愚蠢的错误,但它确实发生了。

Got the same error, but checked all the reasons list here, did not fix my problem.

Finally figure it out that, the reason is that the name of one method that imported but not used yet is not correct. Though it is a stupid error, it happens.


回答 9

我有同样的错误。原来是因为我将我的模块命名为common.py,但是已经有一些其他common.py模块。我要做的就是重命名我的模块。

I had the same error. It turned out to be because I named my module common.py, yet there already was some other common.py module. All I had to do was to rename my module.


回答 10

编写unittest.TestCase时遇到类似的错误。当我按原样重新键入相同的方法定义时,它似乎起作用了!

我在PyCharm上注意到的唯一变化是第二次弹出“覆盖”图标,因为setup(self)方法需要覆盖TestCase中定义的原始方法。

I had a similar error while writing a unittest.TestCase. When I re-typed the same method definition as-is, it seemed to work !

The only change I noticed on PyCharm was the ‘override’ icon pop-up the 2nd time, as the setup(self) method needs to override the original method defined in TestCase.


Python unittest中的setUp()和setUpClass()有什么区别?

问题:Python unittest中的setUp()和setUpClass()有什么区别?

setUp()setUpClass()Python unittest框架之间有什么区别?为什么设置会以一种方法而不是另一种方法处理?

我想了解什么设置的一部分在完成setUp()setUpClass()功能,以及与tearDown()tearDownClass()

What is the difference between setUp() and setUpClass() in the Python unittest framework? Why would setup be handled in one method over the other?

I want to understand what part of setup is done in the setUp() and setUpClass() functions, as well as with tearDown() and tearDownClass().


回答 0

当您的类中有多个测试方法时,差异就会显现出来。setUpClasstearDownClass一旦被全班运行; setUptearDown在每种测试方法之前和之后运行。

例如:

class Example(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("setUpClass")

    def setUp(self):
        print("setUp")

    def test1(self):
        print("test1")

    def test2(self):
        print("test2")

    def tearDown(self):
        print("tearDown")

    @classmethod
    def tearDownClass(cls):
        print("tearDownClass")

运行此测试时,它会打印:

setUpClass
setUp
test1
tearDown
.setUp
test2
tearDown
.tearDownClass

(该点(.)是unittest的默认输出时,测试通过)观察到setUptearDown之前和之后出现test1 test2,而setUpClasstearDownClass只出现一次,在整个测试案例的开始和结束。

The difference manifests itself when you have more than one test method in your class. setUpClass and tearDownClass are run once for the whole class; setUp and tearDown are run before and after each test method.

For example:

class Example(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("setUpClass")

    def setUp(self):
        print("setUp")

    def test1(self):
        print("test1")

    def test2(self):
        print("test2")

    def tearDown(self):
        print("tearDown")

    @classmethod
    def tearDownClass(cls):
        print("tearDownClass")

When you run this test, it prints:

setUpClass
setUp
test1
tearDown
.setUp
test2
tearDown
.tearDownClass

(The dots (.) are unittest‘s default output when a test passes.) Observe that setUp and tearDown appear before and after test1 and test2, whereas setUpClass and tearDownClass appear only once, at the beginning and end of the whole test case.


回答 1

setUp()setUpClass()Python unittest框架之间有什么区别?

主要区别(如本杰明·霍奇森(Benjamin Hodgson)的回答中所述)是setUpClass仅一次调用,即在所有测试之前,而setUp在每次测试之前均被调用。(注意:这同样适用于其他xUnit测试框架中的等效方法,而不仅仅是Python的方法unittest。)

unittest 文档中

setUpClass()

在运行单个类中的测试之前调用的类方法。使用类作为唯一参数调用setUpClass,并且必须将其装饰为classmethod():

@classmethod
def setUpClass(cls):
    ...

和:

setUp()

调用准备测试夹具的方法。在调用测试方法之前立即调用该方法。除了AssertionError或SkipTest之外,此方法引发的任何异常都将被视为错误而不是测试失败。默认实现不执行任何操作。

为什么设置会以一种方法而不是另一种方法处理?

问题的这一部分尚未回答。根据我对Gearon回答的评论,该setUp方法适用于所有测试通用的灯具元素(以避免在每个测试中重复该代码)。我发现这通常很有用,因为删除重复项(通常)可以提高可读性并减少维护负担。

setUpClass方法适用于昂贵的元素,您只需要执行一次即可,例如打开数据库连接,在文件系统上打开临时文件,加载共享库以进行测试等。在每次测试之前进行此类操作会使速度降低测试套件太多,所以我们在所有测试之前只做一次。测试的独立性略有下降,但在某些情况下是必要的优化。可以说,在单元测试中不应该这样做,因为通常可以在不使用真实内容的情况下模拟数据库/文件系统/库/任何东西。因此,我发现这setUpClass几乎是不需要的。但是,在需要测试上述示例(或类似示例)时很有用。

What is the difference between setUp() and setUpClass() in the Python unittest framework?

The main difference (as noted in the answer by Benjamin Hodgson) is that setUpClass is called only once and that is before all the tests, while setUp is called immediately before each and every test. (NB: The same applies to the equivalent methods in other xUnit test frameworks, not just Python’s unittest.)

From the unittest documentation:

setUpClass()

A class method called before tests in an individual class are run. setUpClass is called with the class as the only argument and must be decorated as a classmethod():

@classmethod
def setUpClass(cls):
    ...

and:

setUp()

Method called to prepare the test fixture. This is called immediately before calling the test method; other than AssertionError or SkipTest, any exception raised by this method will be considered an error rather than a test failure. The default implementation does nothing.

Why would setup be handled in one method over the other?

This part of the question has not been answered yet. As per my comment in response to the answer by Gearon, the setUp method is meant for elements of the fixture that are common to all tests (to avoid duplicating that code in each test). I find this is often useful as removing duplication (usually) improves readability and reduces the maintenance burden.

The setUpClass method is for expensive elements that you would rather only have to do once, such as opening a database connection, opening a temporary file on the filesystem, loading a shared library for testing, etc. Doing such things before each test would slow down the test suite too much, so we just do it once before all the tests. This is a slight degradation in the independence of the tests but a necessary optimization in some situations. Arguably, one should not be doing such things in unit tests as it is usually possible to mock the database / filesystem / library / whatever without using the real thing. As such, I find that setUpClass is rarely needed. However, it is useful when testing the above examples (or similar) becomes necessary.


通过命令行从unittest.TestCase运行单个测试

问题:通过命令行从unittest.TestCase运行单个测试

在我们的团队中,我们定义了大多数测试用例,如下所示:

一门“框架”课ourtcfw.py

import unittest

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

    # other stuff that we want to use everywhere

还有很多测试用例,例如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()

当我编写新的测试代码并希望经常运行它并节省时间时,我要做的是在所有其他测试之前放置“ __”。但这很麻烦,使我从正在编写的代码中分散了注意力,并且由此产生的提交噪音实在令人讨厌。

因此,例如,当对进行更改时testItIsHot(),我希望能够做到这一点:

$ python testMyCase.py testItIsHot

unittest运行 testItIsHot()

我该如何实现?

我尝试重写该if __name__ == "__main__":部分,但是由于我是Python的新手,所以我迷失了方向,不停地尝试除方法之外的所有事情。

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, what I do is that I 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 e.g. 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.


回答 0

这可以按照您的建议进行工作-您只需指定类名即可:

python testMyCase.py MyCase.testItIsHot

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

python testMyCase.py MyCase.testItIsHot

回答 1

如果您组织测试用例,即遵循与实际代码相同的组织,并且对同一包中的模块使用相对导入

您还可以使用以下命令格式:

python -m unittest mypkg.tests.test_module.TestClass.test_method
# In your case, this would be:
python -m unittest testMyCase.MyCase.testItIsHot

与此相关的Python3文档:https ://docs.python.org/3/library/unittest.html#command-line-interface

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

Python3 documentation for this: https://docs.python.org/3/library/unittest.html#command-line-interface


回答 2

您可以猜到它可以很好地工作

python testMyCase.py MyCase.testItIsHot

还有另一种方法可以测试testItIsHot

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

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)

回答 3

如果您查看unittest模块的帮助,它将告诉您几种组合,这些组合允许您从模块运行测试用例类,并从测试用例类运行测试方法。

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

它不需要您将a定义unittest.main()为模块的默认行为。

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

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


回答 4

也许对某人会有帮助。如果您只想运行特定类的测试:

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

它在python 3.6中对我有用

Maybe, it will be helpful for somebody. In case you want to run only tests from specific class:

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

It works for me in python 3.6


回答 5

@yarkee的启发,我将其与已经获得的一些代码结合在一起。您也可以从另一个脚本中调用此方法,run_unit_tests()而无需调用命令行即可直接调用该函数,也可以仅通过命令行从中调用它python3 my_test_file.py

import my_test_file
my_test_file.run_unit_tests()

可悲的是,这仅适用于Python 3.3或优于:

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")

跑步者代码:

#! /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()

稍微编辑一下代码,您可以传递一个包含您要调用的所有单元测试的数组:

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

    classes = \
    [
        LineBalancingUnitTests,
    ]

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

和另一个文件:

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 )

或者,您可以使用https://docs.python.org/3/library/unittest.html#load-tests-protocol并在测试模块/文件上定义以下方法:

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

如果要将执行限制为单个测试文件,则只需将测试发现模式设置为定义load_tests()函数的唯一文件。

#! /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() )

参考文献:

  1. 单元测试模块在脚本中时,sys.argv [1]有问题
  2. 有没有办法遍历并执行Python类中的所有功能?
  3. 在python中遍历一个类的所有成员变量

除了最后一个主程序示例,在阅读unittest.main()方法实现后,我想到了以下变体:

  1. 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() )

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 superior:

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 https://docs.python.org/3/library/unittest.html#load-tests-protocol and define the following method on 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:

  1. Problem with sys.argv[1] when unittest module is in a script
  2. Is there a way to loop through and execute all of the functions in a Python class?
  3. 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:

  1. 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() )

回答 6

TL; DR:这很可能会起作用:

python mypkg/tests/test_module.py MyCase.testItIsHot

说明

  • 便捷的方式

    python mypkg/tests/test_module.py MyCase.testItIsHot

    可以正常工作,但它的潜台词是,您已经在测试文件中(通常在末尾)包含了此常规代码段。

    if __name__ == "__main__":
        unittest.main()
  • 不便的方式

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

    将始终有效,而无需if __name__ == "__main__": unittest.main()在测试源文件中包含该代码段。

那么为什么第二种方法不方便?因为手动键入该长的,用点分隔的路径会很麻烦(_ 在此处插入您的身体部位之一)。在第一种方法中,mypkg/tests/test_module.py可以使用现代外壳或编辑器自动完成零件。

PS:如果您认为身体部位在腰部以下,那么您就是一个真实的人。:-)我的意思是说“手指关节”。打字过多对您的关节不利。;-)

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 does the 2nd method considered inconvenient? Because it would be a pain in the (_ insert one of your body part here _) to type that long, dot-delimited path by hand. While in 1st method, the mypkg/tests/test_module.py part can be auto-completed, either by a modern shell, or by your editor.

PS: If you thought that body part is somewhere below your waist, you are an authentic person. :-) I mean to say “finger joint”. Too much typing would be bad for your joints. ;-)


如何在目录中运行所有Python单元测试?

问题:如何在目录中运行所有Python单元测试?

我有一个目录,其中包含我的Python单元测试。每个单元测试模块的形式为test _ *。py。我正在尝试制作一个名为all_test.py的文件,您猜对了,它将以上述测试形式运行所有文件并返回结果。到目前为止,我已经尝试了两种方法。都失败了。我将展示这两种方法,并希望那里的人知道如何正确地正确执行此操作。

对于我的第一次英勇尝试,我想:“如果我只是将所有测试模块导入文件中,然后调用此unittest.main()doodad,它将起作用,对吗?” 好吧,原来我错了。

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

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

这没有用,我得到的结果是:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

对于第二次尝试,我还是可以,也许我会尝试以“手动”方式进行整个测试。所以我尝试在下面这样做:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

这也没有用,但是似乎太接近了!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

我似乎有一套类似的套件,可以执行结果。我有点担心它说我只有一个事实run=1,似乎应该是这样run=2,但这是进步。但是如何传递结果并将其显示给main?还是我基本上如何使它工作,以便我可以运行该文件,然后运行此目录中的所有单元测试?

I have a directory that contains my Python unit tests. Each unit test module is of the form test_*.py. I am attempting to make a file called all_test.py that will, you guessed it, run all files in the aforementioned test form and return the result. I have tried two methods so far; both have failed. I will show the two methods, and I hope someone out there knows how to actually do this correctly.

For my first valiant attempt, I thought “If I just import all my testing modules in the file, and then call this unittest.main() doodad, it will work, right?” Well, turns out I was wrong.

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

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

This did not work, the result I got was:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

For my second try, I though, ok, maybe I will try to do this whole testing thing in a more “manual” fashion. So I attempted to do that below:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

This also did not work, but it seems so close!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

I seem to have a suite of some sort, and I can execute the result. I am a little concerned about the fact that it says I have only run=1, seems like that should be run=2, but it is progress. But how do I pass and display the result to main? Or how do I basically get it working so I can just run this file, and in doing so, run all the unit tests in this directory?


回答 0

使用Python 2.7及更高版本,您无需编写新代码或使用第三方工具即可完成此操作。内置了通过命令行执行递归测试的功能。__init__.py在您的测试目录中放置:

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

您可以在python 2.7python 3.x unittest文档中阅读更多内容。

With Python 2.7 and higher you don’t have to write new code or use third-party tools to do this; recursive test execution via the command line is built-in. Put an __init__.py in your test directory and:

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

You can read more in the python 2.7 or python 3.x unittest documentation.


回答 1

您可以使用可以为您完成此任务的测试运行程序。 例如鼻子很好。运行时,它将在当前树中找到测试并运行它们。

更新:

这是我前鼻时期的一些代码。您可能不希望使用模块名称的明确列表,但是其余的名称可能对您有用。

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

You could use a test runner that would do this for you. nose is very good for example. When run, it will find tests in the current tree and run them.

Updated:

Here’s some code from my pre-nose days. You probably don’t want the explicit list of module names, but maybe the rest will be useful to you.

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

回答 2

在python 3中,如果您使用的是unittest.TestCase

  • 您的目录中必须有一个空(或其他)__init__.py文件test必须命名为test/
  • 您的测试文件test/与模式匹配test_*.py。它们可以位于下方的子目录中test/,并且这些子目录可以命名为任何东西。

然后,您可以使用以下命令运行所有测试:

python -m unittest

做完了!解决方案少于100行。希望其他python初学者可以通过查找此方法节省时间。

In python 3, if you’re using unittest.TestCase:

  • You must have an empty (or otherwise) __init__.py file in your test directory (must be named test/)
  • Your test files inside test/ match the pattern test_*.py. They can be inside a subdirectory under test/, and those subdirs can be named as anything.

Then, you can run all the tests with:

python -m unittest

Done! A solution less than 100 lines. Hopefully another python beginner saves time by finding this.


回答 3

现在,可以直接从unittest:unittest.TestLoader.discover进行此操作

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)

This is now possible directly from unittest: unittest.TestLoader.discover.

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)

回答 4

通过研究上面的代码(特别是使用TextTestRunnerdefaultTestLoader),我可以很接近了。最终,我通过仅将所有测试套件传递给单个套件的构造函数,而不是“手动”添加它们来修复了我的代码,从而解决了其他问题。所以这是我的解决方案。

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

是的,使用鼻子可能比这样做更容易,但这不重要。

Well by studying the code above a bit (specifically using TextTestRunner and defaultTestLoader), I was able to get pretty close. Eventually I fixed my code by also just passing all test suites to a single suites constructor, rather than adding them “manually”, which fixed my other problems. So here is my solution.

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

Yeah, it is probably easier to just use nose than to do this, but that is besides the point.


回答 5

如果要运行各种测试用例类中的所有测试,并且很乐意明确指定它们,则可以这样做:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

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

uclid我的项目在哪里,TestSymbols并且TestPatterns是的子类TestCase

If you want to run all the tests from various test case classes and you’re happy to specify them explicitly then you can do it like this:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

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

where uclid is my project and TestSymbols and TestPatterns are subclasses of TestCase.


回答 6

我使用了该discover方法和的重载load_tests来在(最少,我认为)数字行中实现此结果:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

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

执行击掌,像

Ran 27 tests in 0.187s
OK

I have used the discover method and an overloading of load_tests to achieve this result in a (minimal, I think) number lines of code:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

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

Execution on fives something like

Ran 27 tests in 0.187s
OK

回答 7

我尝试了各种方法,但似乎都存在缺陷,或者我必须编写一些代码,这很烦人。但是在Linux下,有一种简便的方法,就是简单地通过某种模式找到每个测试,然后逐个调用它们。

find . -name 'Test*py' -exec python '{}' \;

最重要的是,它肯定有效。

I tried various approaches but all seem flawed or I have to makeup some code, that’s annoying. But there’s a convinient way under linux, that is simply to find every test through certain pattern and then invoke them one by one.

find . -name 'Test*py' -exec python '{}' \;

and most importantly, it definitely works.


回答 8

对于打包的库或应用程序,您不想这样做。setuptools 会为你做

要使用此命令,您的项目的测试必须unittest通过函数,TestCase类或方法或包含TestCase类的模块或包来包装在测试套件中。如果命名套件是一个模块,并且该模块具有一个additional_tests()功能,则将其调用,并将结果(必须为unittest.TestSuite)添加到要运行的测试中。如果命名套件是一个包,则将所有子模块和子包递归地添加到整个测试套件中

只需告诉它您的根测试包在哪里,例如:

setup(
    # ...
    test_suite = 'somepkg.test'
)

然后跑 python setup.py test

除非您避免在测试套件中进行相对导入,否则在Python 3中基于文件的发现可能会出现问题,因为会discover使用文件导入。即使它支持optional top_level_dir,但是我还是有一些无限递归错误。因此,针对非打包代码的简单解决方案是将以下内容放入__init__.py测试包中(请参阅load_tests协议)。

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite

In case of a packaged library or application, you don’t want to do it. setuptools will do it for you.

To use this command, your project’s tests must be wrapped in a unittest test suite by either a function, a TestCase class or method, or a module or package containing TestCase classes. If the named suite is a module, and the module has an additional_tests() function, it is called and the result (which must be a unittest.TestSuite) is added to the tests to be run. If the named suite is a package, any submodules and subpackages are recursively added to the overall test suite.

Just tell it where your root test package is, like:

setup(
    # ...
    test_suite = 'somepkg.test'
)

And run python setup.py test.

File-based discovery may be problematic in Python 3, unless you avoid relative imports in your test suite, because discover uses file import. Even though it supports optional top_level_dir, but I had some infinite recursion errors. So a simple solution for a non-packaged code is to put the following in __init__.py of your test package (see load_tests Protocol).

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite

回答 9

我使用PyDev / LiClipse,但还没有真正弄清楚如何从GUI一次运行所有测试。(编辑:右键单击根测试文件夹,然后选择Run as -> Python unit-test

这是我当前的解决方法:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

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

我把这段代码放在一个名为 all测试目录中。如果我以LiClipse的单元测试形式运行此模块,则将运行所有测试。如果我只要求重复特定或失败的测试,则仅运行那些测试。它也不会干扰我的命令行测试运行程序(noestests)-被忽略。

您可能需要discover根据项目设置将参数更改为。

I use PyDev/LiClipse and haven’t really figured out how to run all tests at once from the GUI. (edit: you right click the root test folder and choose Run as -> Python unit-test

This is my current workaround:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

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

I put this code in a module called all in my test directory. If I run this module as a unittest from LiClipse then all tests are run. If I ask to only repeat specific or failed tests then only those tests are run. It doesn’t interfere with my commandline test runner either (nosetests) — it’s ignored.

You may need to change the arguments to discover based on your project setup.


回答 10

根据Stephen Cagle的回答,我添加了对嵌套测试模块的支持。

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

该代码搜索的所有子目录以.查找*Tests.py文件,然后将其加载。它期望每个*Tests.py都包含一个类*Tests(unittest.TestCase),该类依次加载并一个接一个地执行。

这适用于目录/模块的任意深度嵌套,但是之间的每个目录__init__.py至少需要包含一个空文件。这允许测试通过用点替换斜杠(或反斜杠)来加载嵌套模块(请参阅参考资料replace_slash_by_dot)。

Based on the answer of Stephen Cagle I added support for nested test modules.

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

The code searches all subdirectories of . for *Tests.py files which are then loaded. It expects each *Tests.py to contain a single class *Tests(unittest.TestCase) which is loaded in turn and executed one after another.

This works with arbitrary deep nesting of directories/modules, but each directory in between needs to contain an empty __init__.py file at least. This allows the test to load the nested modules by replacing slashes (or backslashes) by dots (see replace_slash_by_dot).


回答 11

这是一个老问题,但现在(2019年)对我有用的是:

python -m unittest *_test.py

我所有的测试文件都与源文件位于同一文件夹中,并以结尾_test

This is an old question, but what worked for me now (in 2019) is:

python -m unittest *_test.py

All my test files are in the same folder as the source files and they end with _test.


回答 12

由于测试发现似乎是一个完整的主题,因此存在一些专用的框架来测试发现:

在这里更多阅读:https : //wiki.python.org/moin/PythonTestingToolsTaxonomy

Because Test discovery seems to be a complete subject, there is some dedicated framework to test discovery :

More reading here : https://wiki.python.org/moin/PythonTestingToolsTaxonomy


回答 13

无论您位于哪个工作目录中,此BASH脚本都将从文件系统中的任何位置执行python unittest测试目录:其工作目录始终test位于该目录所在的位置。

所有测试,独立$ PWD

unittest Python模块对您的当前目录敏感,除非您告诉它当前位置(使用discover -s选项)。

当位于./src./example工作目录中并且需要快速的整体单元测试时,这很有用:

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

所选测试,独立$ PWD

我将此实用程序文件命名为:runone.py并按以下方式使用它:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

test/__init__.py在生产过程中不需要文件来负担您的程序包/内存开销。

This BASH script will execute the python unittest test directory from ANYWHERE in the file system, no matter what working directory you are in: its working directory always be where that test directory is located.

ALL TESTS, independent $PWD

unittest Python module is sensitive to your current directory, unless you tell it where (using discover -s option).

This is useful when staying in the ./src or ./example working directory and you need a quick overall unit test:

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

SELECTED TESTS, independent $PWD

I name this utility file: runone.py and use it like this:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

No need for a test/__init__.py file to burden your package/memory-overhead during production.


回答 14

这是我创建包装器以从命令行运行测试的方法:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

为了简单起见,请原谅我的非PEP8编码标准。

然后,您可以为所有测试的通用组件创建BaseTest类,因此每个测试都将看起来像:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

要运行,只需将测试指定为命令行参数的一部分,例如:

./run_tests.py -h http://example.com/ tests/**/*.py

Here is my approach by creating a wrapper to run tests from the command line:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

For sake of simplicity, please excuse my non-PEP8 coding standards.

Then you can create BaseTest class for common components for all your tests, so each of your test would simply look like:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

To run, you simply specifying tests as part of the command line arguments, e.g.:

./run_tests.py -h http://example.com/ tests/**/*.py