unittest
且任容枯 Lv4

基本概念

  • test fixture 测试夹具

    测试预先操作以及测试的后置操作

  • test case 测试用例

    测试用例 unittes 提供一个基础类TestCase

  • test suit 测试套

    测试用例的集合容器,聚集要一起执行的用例

  • test runner 测试运行器

    协调测试执行并向用户提供结果的组件

simlpe example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import unittest

class TestNumber(unittest.TestCase):
def test_01(self):
self.assertEqual(1, 1)

def test_02(self):
self.assertFalse(1==2)

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

# ..
# ----------------------------------------------------------------------
# Ran 2 tests in 0.000s

# OK
  • 测试类继承unittest.Testcase
  • 测试方法名test开头

命令行接口

1
2
3
4
python -m unittest test_module1 test_module2
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method
python -m unittest tests/test_something.py
1
2
3
4
5
6
(.venv) PS D:\project\howtospider> python -m unittest main.TestNumber.test_01
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

当没有参数时候 测试发现被启动

命令行选项 以及测试发现选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(.venv) PS D:\project\howtospider> python -m unittest -h
usage: python.exe -m unittest [-h] [-v] [-q] [--locals] [--durations N] [-f] [-c] [-b] [-k TESTNAMEPATTERNS] [tests ...]

positional arguments:
tests a list of any number of test modules, classes and test methods.

options:
-h, --help show this help message and exit
-v, --verbose Verbose output 显示详细信息
-q, --quiet Quiet output 显示简略信息
--locals Show local variables in tracebacks 在tracebacks中显示变量
--durations N Show the N slowest test cases (N=0 for all) 显示最慢的几个测试用例
-f, --failfast Stop on first fail or error 遇到第一个失败的用例就停止
-c, --catch Catch Ctrl-C and display results so far 按ctrl c 运行完当前用例结束则返回测试结果, 再按ctrl c 就触发KeyboardInterrupt异常
-b, --buffer Buffer stdout and stderr during tests 缓存标准输出 错误输出
-k TESTNAMEPATTERNS Only run tests which match the given substring 运行符合关键词的用例

Examples:
python.exe -m unittest test_module - run tests from test_module
python.exe -m unittest module.TestClass - run tests from module.TestClass
python.exe -m unittest module.Class.test_method - run specified test method
python.exe -m unittest path/to/test_file.py - run tests from test_file.py

usage: python.exe -m unittest discover [-h] [-v] [-q] [--locals] [--durations N] [-f] [-c] [-b] [-k TESTNAMEPATTERNS] [-s START] [-p PATTERN] [-t TOP]

options:
-h, --help show this help message and exit
-v, --verbose Verbose output
-q, --quiet Quiet output
--locals Show local variables in tracebacks
--durations N Show the N slowest test cases (N=0 for all)
-f, --failfast Stop on first fail or error
-c, --catch Catch Ctrl-C and display results so far
-b, --buffer Buffer stdout and stderr during tests
-k TESTNAMEPATTERNS Only run tests which match the given substring
-s START, --start-directory START
Directory to start discovery ('.' default)
-p PATTERN, --pattern PATTERN
Pattern to match tests ('test*.py' default)
-t TOP, --top-level-directory TOP
Top level directory of project (defaults to start directory)

For test discovery all test modules must be importable from the top level directory of the project.
1
2
python -m unittest discover -s project_directory -p "*_test.py"
python -m unittest discover project_directory "*_test.py"

组织测试

测试夹具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import unittest

class TestNumber(unittest.TestCase):
def setUp(self):
print('setUp-----------')

def test_01(self):
print('test_01')
self.assertEqual(1, 1)

def test_02(self):
print('test_02')
self.assertFalse(1==2)

def tearDown(self):
print('tearDown---------')

# setUp-----------
# test_01
# tearDown---------
# .setUp-----------
# test_02
# tearDown---------
# .
# ----------------------------------------------------------------------
# Ran 2 tests in 0.001s

# OK

testsuit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pdb import run
import unittest

class TestCase01(unittest.TestCase):
def test_01(self):
print('TestCase01_test_01')

def test_02(self):
print('TestCase01_test_02')


class TestCase02(unittest.TestCase):
def test_01(self):
print('TestCase02_test_01')

def test_02(self):
print('TestCase02_test_02')


def suite():
suite = unittest.TestSuite()
suite.addTest(TestCase01('test_01'))
suite.addTest(TestCase02('test_01'))
return suite

if __name__ == "__main__":
runner = unittest.TextTestRunner()
runner.run(suite())

# TestCase01_test_01
# .TestCase02_test_01
# .
# ----------------------------------------------------------------------
# Ran 2 tests in 0.001s

# OK

function to TestCase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pdb import run
import unittest

def testsomething():
assert 1 == 1

def beforetest():
print('beforetest')

def aftertest():
print('aftertest')

testcase = unittest.FunctionTestCase(testsomething, setUp=beforetest, tearDown=aftertest)


def suite():
suite = unittest.TestSuite()
suite.addTest(testcase)
return suite

if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())

# beforetest
# aftertest
# .
# ----------------------------------------------------------------------
# Ran 1 test in 0.001s

# OK

跳过用例和预期错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# @unittest.skip("showing class skipping")
class MyTestCase(unittest.TestCase):

@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")

@unittest.skipIf(mylib.__version__ < (1, 3),
"not supported in this library version")
def test_format(self):
# Tests that work for only a certain version of the library.
pass

@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_windows_support(self):
# windows specific testing code
pass

def test_maybe_skipped(self):
if not external_resource_available():
self.skipTest("external resource not available")
# test code that depends on the external resource
pass
1
2
3
4
5
6
7
8
9
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
# 自定义装饰器
def skipUnlessHasattr(obj, attr):
if hasattr(obj, attr):
return lambda func: func
return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))

子测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class NumbersTest(unittest.TestCase):

def test_even(self):
"""
Test that numbers between 0 and 5 are all even.
"""
for i in range(0, 6):
with self.subTest(i=i):
self.assertEqual(i % 2, 0)

# FFF
# ======================================================================
# FAIL: test_even (main.NumbersTest.test_even) (i=1)
# Test that numbers between 0 and 5 are all even.
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "D:\project\howtospider\main.py", line 11, in test_even
# self.assertEqual(i % 2, 0)
# AssertionError: 1 != 0

# ======================================================================
# FAIL: test_even (main.NumbersTest.test_even) (i=3)
# Test that numbers between 0 and 5 are all even.
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "D:\project\howtospider\main.py", line 11, in test_even
# self.assertEqual(i % 2, 0)
# AssertionError: 1 != 0

# ======================================================================
# FAIL: test_even (main.NumbersTest.test_even) (i=5)
# Test that numbers between 0 and 5 are all even.
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "D:\project\howtospider\main.py", line 11, in test_even
# self.assertEqual(i % 2, 0)
# AssertionError: 1 != 0

# ----------------------------------------------------------------------
# Ran 1 test in 0.001s

# FAILED (failures=3)