unittest
单元测试框架是受到 JUnit
的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化、支持将测试样例聚合到测试集中,并将测试与报告框架独立开来。unittest
模块是Python标准库中的模块。在此之前需要先了解几个概念:
-
test fixture——测试固件
表示为了开展一项或多项测试所需要进行的准备工作,以及所有相关的清理操作。举个例子,你的测试需要启动一个数据库,这时,就可以在
setUp()
方法中连接数据库,在tearDown()
方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码。即测试固件就是整合了代码的公共部分。 -
test case——测试用例
一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。
unittest
提供一个基类:TestCase
,用于新建测试用例。 -
test suite——测试套件
test suite
是一系列的测试用例,或测试套件,或两者皆有。它用于归档需要一起执行的测试。 -
test runner——测试运行器
test runner
是给测试用例提供运行环境的,通过它的run()
方法来执行测试用例,并在执行完成后将测试结果输出。
语法
在unittest
模块中,需要通过继承TestCase
类来构建单元测试用例,具体语法如下:
class 测试类名(unitest.TestCase):
测试用例
可以一个测试用例生成一个类,也可以多个测试用例生成一个类,考虑到执行效率的问题,
建议使用后者来构建测试用例。
class 测试类名(unittest.TestCase):
测试用例1
测试用例2
测试用例3
一个测试用例可以通过定义一个函数完成,将执行测试的代码封装到函数内,最后通过TaseCase
类中的断言来判断测试是否通过,常用的断言方法有以下几种:
assertEqual
(预期值,实际值)当两者相等的时候测试通过。assertNotEqual
(预期值,实际值)当两者不相等的时候测试通过。assertTrue
(表达式)当表达式为真的时候测试通过。assertFalse
(表达式)当表达式为假的时候测试通过
下面用一段代码演示一下:
# test_demo01.py
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(),'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('FOO'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(),['hello','wolrd'])
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()
执行结果:
FF.
======================================================================
FAIL: test_isupper (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\unittest\test_demo01.py", line 9, in test_isupper
self.assertFalse('FOO'.isupper())
AssertionError: True is not false
======================================================================
FAIL: test_split (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\unittest\test_demo01.py", line 14, in test_split
self.assertEqual(s.split(),['hello','wolrd'])
AssertionError: Lists differ: ['hello', 'world'] != ['hello', 'wolrd']
First differing element 1:
'world'
'wolrd'
- ['hello', 'world']
? -
+ ['hello', 'wolrd']
? +
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=2)
继承 unittest.TestCase
就创建了一个测试用例。上述三个独立的测试是三个类的方法,这些方法的命名都以 test 开头。 这个命名约定告诉测试运行者类的哪些方法表示测试用例。
每个测试的关键是:调用 assertEqual()
来检查预期的输出; 调用assertTrue()
或 assertFalse()
来验证一个条件;调用 assertRaises()
来验证抛出了一个特定的异常。使用这些方法而不是 assert
语句是为了让测试运行者能聚合所有的测试结果并产生结果报告。
通过 setUp()
和 tearDown()
方法,可以设置测试开始前与完成后需要执行的指令。 在 组织你的测试代码 中,对此有更为详细的描述。
最后的代码块中,演示了运行测试的一个简单的方法。 unittest.main()
提供了一个测试脚本的命令行接口。当在命令行运行该测试脚本,上文的脚本生成如以下格式的输出:
test_isupper (test_demo01.TestStringMethods) ... FAIL
test_split (test_demo01.TestStringMethods) ... FAIL
test_upper (test_demo01.TestStringMethods) ... ok
======================================================================
FAIL: test_isupper (test_demo01.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\unittest\test_demo01.py", line 10, in test_isupper
self.assertFalse('FOO'.isupper())
AssertionError: True is not false
======================================================================
FAIL: test_split (test_demo01.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\unittest\test_demo01.py", line 14, in test_split
self.assertEqual(s.split(),['hello','wolrd'])
AssertionError: Lists differ: ['hello', 'world'] != ['hello', 'wolrd']
First differing element 1:
'world'
'wolrd'
- ['hello', 'world']
? -
+ ['hello', 'wolrd']
? +
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=2)
注意:
-v
参数在PyCharm中,可以点击Run
-Edit Configurations
-Parameters
中进行添加,如果是命令行形式运行的话,就直接在指令后加上即可。
命令行界面
unittest 模块可以通过命令行运行模块、类和独立测试方法的测试:
python -m unittest test_demo01 TestStringMethods #运行TestStringMethods类下的所有测试用例
python -m unittest test_demo01.TestStringMethods #运行TestStringMethods类下的所有测试用例
python -m unittest test_demo01.TestStringMethods.test_upper #运行TestStringMethods类下名为‘test_upper’的测试用例
如上,你可以传入模块名、类或方法名或它们的任意组合。
同样的,测试模块可以通过文件路径指定:
python -m unittest UnitTest/test_demo01.py
这样就可以使用 shell 的文件名补全指定测试模块。所指定的文件仍需要可以被作为模块导入。路径通过去除 .py
、把分隔符转换为 .
转换为模块名。若你需要执行不能被作为模块导入的测试文件,你需要直接执行该测试文件。
在运行测试时,你可以通过添加 -v 参数获取更详细(更多的冗余)的信息:
python -m unittest -v test_demo01
当运行时不包含参数,开始 探索性测试:
python -m unittest
此命令会对当前目录下所有的文件进行测试
用于获取命令行选项列表:
python -m unittest -h
命令行选项
unittest
支持以下命令行选项:
-
-b, --buffer
在测试运行时,标准输出流与标准错误流会被放入缓冲区。成功的测试的运行时输出会被丢弃;测试不通过时,测试运行中的输出会正常显示,错误会被加入到测试失败信息。
-
-c, --catch
当测试正在运行时, Control-C 会等待当前测试完成,并在完成后报告已执行的测试的结果。当再次按下 Control-C 时,引发平常的
KeyboardInterrupt
异常。 -
-f, --failfast
当出现第一个错误或者失败时,停止运行测试。 -
-k
只运行匹配模式或子串的测试方法和类。可以多次使用这个选项,以便包含匹配子串的所有测试用例。包含通配符(*)的模式使用
fnmatch.fnmatchcase()
对测试名称进行匹配。另外,该匹配是大小写敏感的。模式对测试加载器导入的测试方法全名进行匹配。例如,
-k foo
可以匹配到foo_tests.SomeTest.test_something
和bar_tests.SomeTest.test_foo
,但是不能匹配到bar_tests.FooTest.test_something
。 -
–locals
在回溯中显示局部变量。
命令行亦可用于探索性测试,以运行一个项目的所有测试或其子集。
探索性测试
unittest
支持简单的测试搜索。若需要使用探索性测试,所有的测试文件必须是 modules
或 packages
(包括 namespace packages
)并可从项目根目录导入(即它们的文件名必须是有效的 identifiers
)。
探索性测试在 TestLoader.discover()
中实现,但也可以通过命令行使用。它在命令行中的基本用法如下:
cd project_directory
python -m unittest discover
注意:方便起见,
python -m unittest
与python -m unittest discover
等价。如果你需要向探索性测试传入参数,必须显式地使用discover子命令。discover有以下选项:
- -v, --verbose:更详细地输出结果。
- -s, --start-directory directory:开始进行搜索的目录(默认值为当前目录 . )。
- -p, --pattern pattern:用于匹配测试文件的模式(默认为 test*.py )。
- -t, --top-level-directory directory:指定项目的最上层目录(通常为开始时所在目录)。
-s
,-p
和 -t
选项可以按顺序作为位置参数传入。以下两条命令是等价的:
python -m unittest discover -s project_director -p "_test.py"
python -m unittest discover project_director -p "_test.py"
正如可以传入路径那样,传入一个包名作为起始目录也是可行的,如 myproject.subpackage.test
。你提供的包名会被导入,它在文件系统中的位置会被作为起始目录。
警告 探索性测试通过导入测试对测试进行加载。在找到所有你指定的开始目录下的所有测试文件后,它把路径转换为包名并进行导入。如 foo/bar/baz.py
会被导入为 foo.bar.baz
。
如果你有一个全局安装的包,并尝试对这个包的副本进行探索性测试,可能会从错误的地方开始导入。如果出现这种情况,测试会输出警告并退出。
如果你使用包名而不是路径作为开始目录,搜索时会假定它导入的是你想要的目录,所以你不会收到警告。
测试模块和包可以通过 load_tests protocol
自定义测试的加载和搜索。
用例组织
单元测试的构建单位是 test cases
:独立的、包含执行条件与正确性检查的方案。在 unittest
中,测试用例表示为 unittest.TestCase
的实例。通过编写 TestCase
的子类或使用 FunctionTestCase
编写你自己的测试用例。
一个 TestCase
实例的测试代码必须是完全自含的,因此它可以独立运行,或与其它任意组合任意数量的测试用例一起运行。
TestCase
的最简单的子类需要实现一个测试方法(例如一个命名以 test
开头的方法)以执行特定的测试代码:
import unittest
class DefaultWidgetSizeTestCase(unittest.TestCase):
def test_default_widget_size(self):
widget = widget("The widget")
self.assertEqual(widget.size(),(50,50))
可以看到,为了进行测试,我们使用了基类 TestCase
提供的其中一个 assert*()
方法。若测试不通过,将会引发一个带有说明信息的异常,并且 unittest
会将这个测试用例标记为测试不通过。任何其它类型的异常将会被当做错误处理。
可能同时存在多个前置操作相同的测试,我们可以把测试的前置操作从测试代码中拆解出来,并实现测试前置方法 setUp()
。在运行测试时,测试框架会自动地为每个单独测试调用前置方法。
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
def test_widget_resize(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
注意:多个测试运行的顺序由内置字符串排序方法对测试名进行排序的结果决定。
在测试运行时,若 setUp() 方法引发异常,测试框架会认为测试发生了错误,因此测试方法不会被运行。
相似的,我们提供了一个 tearDown()
方法在测试方法运行后进行清理工作。
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
若 setUp()
成功运行,无论测试方法是否成功,都会运行 tearDown()
。
这样的一个测试代码运行的环境被称为 test fixture
。一个新的 TestCase
实例作为一个测试脚手架,用于运行各个独立的测试方法。在运行每个测试时,setUp()
、tearDown()
和 init()
会被调用一次。
建议使用 TestCase
实现根据测试的功能将测试分组在一起。单元测试为此提供了一种机制:测试套件,由单元测试的TestSuite
类表示。在大多数情况下,调用unittest.main()
将做正确的事并收集模块的所有测试用例并执行它们。
然而,如果你需要自定义你的测试套件的话,你可以参考以下方法组织你的测试:
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase('test_default_widget_size'))
suite.addTest(WidgetTestCase('test_widget_resize'))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
你可以将测试用例和测试套件的定义放在被测代码码相同的模块(如widget.py)中, ,但将测试代码放在单独的模块(如test_widget.py)中有以下几个优点 :
- 测试模块可以从命令行独立运行。
- 测试代码可以更容易地与已发货代码分离。
- 在没有充分理由的情况下更改测试代码以适应其测试的代码的诱惑较小。
- 测试代码的修改频率应比测试的代码少得多。
- 测试的代码可以更容易地重构。
- 不管怎样,用 C 编写的模块的测试必须在单独的模块中,那么为什么不保持一致呢?
- 如果测试策略发生更改,则无需更改源代码。
复用已有的测试代码
一些用户希望直接使用 unittest
运行已有的测试代码,而不需要把已有的每个测试函数转化为一个 TestCase
的子类。
因此, unittest
提供 FunctionTestCase
类。这个 TestCase
的子类可用于打包已有的测试函数,并支持设置前置与后置函数。
假定有一个测试函数:
def testSomething():
something = makeSomething()
assert something.name is not None
# ....
可以创建等价的测试用例如下,其中前置和后置方法是可选的。
testcase = unittest.FunctionTestCase(testSomething,
setUp=makeSomethingDB,
tearDown=deleteSomethingDB)
注意:尽管函数测试案例可用于将现有测试库快速转换为基于单元测试的系统,但不建议使用此方法。花时间设置正确的
TestCase
子类将使未来的测试重构变得无限容易。
在某些情况下,现有的测试可能是用 doctest
模块编写的。 如果是这样, doctest
提供了一个 DocTestSuite
类,可以从现有基于 doctest
的测试中自动构建 unittest.TestSuite
用例。
跳过及预期失败
unittest
支持跳过单个测试方法,甚至整个测试类。此外,它支持将测试标记为"预期失败",该测试已损坏且将失败,但不应算作测试结果的失败。跳过测试只是使用skip()
修饰器或其条件变体之一、在 setUp()
或测试方法中调用TestCase.skipTest()
或直接引发SkipTest
的问题。
以下装饰器和异常实现了测试方法及测试类的跳过和测试方法预期的失败:
-
@
unittest.skip(reason)
无条件跳过装饰测试。原因应说明为何跳过测试
-
@unittest.skipIf(condition,reason)
如果条件为真,则跳过修饰的测试。
-
@unittest.skipUnless(condition,reason)
除非条件为真,否则跳过装饰性测试。
-
@unittest.expectedFailure
将测试标记为预期的失败。如果测试失败,则视为成功。如果测试通过,将被视为失败。
-
exception unittest.SkipTest(reason)
引发此异常以跳过测试。
通常来说,你可以使用
TestCase.skipTest()
或跳过测试的装饰器实现跳过测试的功能,而不是直接引发此异常。
注意:
- 被跳过的测试的
setUp()
和tearDown()
不会被运行。- 被跳过的类的
setUpClass()
和tearDownClass()
不会被运行。- 被跳过的模组的
setUpModule()
和tearDownModule()
不会被运行。
跳过测试的基本用法如下:
import unittest
import sys
import pytest
class MyTestCase(unittest.TestCase):
Count = 0
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.skipIf(pytest.__version__ in ("5.4.3","5.4.4"),"not supported in this library version")
def test_format(self):
# Tests that work for only a certain version of the library.
print(pytest.__version__)
print(type(pytest.__version__))
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 self.Count == 0:
# self.skipTest("external resource not available")
if not external_resource_available():
self.skipTest("external resource not available")
# test code that depends on the external resource
pass
if __name__ == "__main__":
unittest.main()
在详细模式下运行以上测试例子时,程序输出如下:
7.0.1
<class 'str'>
.ss.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK (skipped=2)
跳过测试类的写法跟跳过测试方法的写法相似:
@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
def test_not_run(self):
pass
TestCase.setUp()
也可以跳过测试。可以用于所需资源不可用的情况下跳过接下来的测试。
使用 expectedFailure()
装饰器表明这个测试预计失败:
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
当要跳过测试时,通过创建在测试中调用skip() 的修饰器,可以轻松滚动自己的跳过修饰器。此修饰器将跳过测试,除非传递的对象具有特定属性:
def skipUnlessHasattr(obj, attr):
if hasattr(obj, attr):
return lambda func: func
return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))
使用subTest区分测试迭代
如果在测试方法内,我们还需要对某些差异非常小的参数进行测试,unittest
允许你使用subTest()
上下文管理器在测试方法的主体内区分它们。即可以在测试方法的内部进行调用测试方法进行测试,可以传递一个参数(数据驱动)对子测试方法进行测试,子测试方法会针对参数集(数据驱动)中的每一个参数值,进行测试结果的输出。例如:
import unittest
class NumberTest(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)
if __name__ == "__main__":
unittest.main()
可以得到以下输出:
======================================================================
FAIL: test_even (__main__.NumberTest) (i=1)
Test that numbers between 0 and 5 are all even
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\UnitTest\test_demo05.py", line 12, in test_even
self.assertEqual(i % 2,0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumberTest) (i=3)
Test that numbers between 0 and 5 are all even
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\UnitTest\test_demo05.py", line 12, in test_even
self.assertEqual(i % 2,0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumberTest) (i=5)
Test that numbers between 0 and 5 are all even
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\UnitTest\test_demo05.py", line 12, in test_even
self.assertEqual(i % 2,0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=3)
如果不使用子测试,则在第一次失败后执行将停止,并且错误将不那么容易诊断,因为i
不会显示具体值:
import unittest
class NumberTest(unittest.TestCase):
def test_even(self):
"""
Test that numbers between 0 and 5 are all even
"""
for i in range(0,6):
self.assertEqual(i % 2,0)
if __name__ == "__main__":
unittest.main()
F
======================================================================
FAIL: test_even (__main__.NumberTest)
Test that numbers between 0 and 5 are all even
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Code\PycharmProjects\unittest\test_demo05.py", line 12, in test_even
self.assertEqual(i % 2,0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
8. unittest
中的类与函数
8.1 测试用例
8.1.1 TestCase类
class unittest.TestCase(methodName='runTest')
TestCase
这个类的作用是用于基类当中,具体的测试方法由子类实现。此类实现测试运行程序所需要的接口,使其能够完成驱动测试,每个实例TestCase
将运行一个基本方法:名为methodName
的方法。在的大多数用法中TestCase
,既不会更改methodName
也不会重新实现默认runTest()
方法。
TestCase
实例提供了三组方法:
-
第一组用于运行测试
setUp()
:调用准备测试夹具的方法,在调用测试方法之前立即调用该方法。tearDown()
:调用测试方法并记录结果后立即调用的方法。setUpClass()
:在运行单个类中的测试之前调用的类方法。setUpClass
以类作为唯一参数调用,并且必须修饰为classmethod()
。在方法上使用@classmethod
装饰器装饰。tearDownClass()
:在单个类中的测试运行后调用的类方法。tearDownClass
以类作为唯一参数调用,并且必须修饰为classmethod()
。在方法上使用@classmethod
装饰器装饰。run(result=None)
:运行测试,将结果收集到TestResult
作为result传递的对象中。如果省略result
或None
,则将创建一个临时结果对象(通过调用该defaultTestResult()
方法)并将其使用。结果对象返回给run()的调用者。skipTest(reason)
:在测试方法期间调用此方法,reason
输入原因。subTest(msg=None,**params)
:返回一个上下文管理器,该上下文管理器将附带的代码块作为子测试执行。msg
和params
是可选的,msg
可以为任意值,在子测试失败时会显示这些值,使您可以清楚地识别它们。debug()
:运行测试而不收集结果。这样可以将测试引发的异常传播到调用方,并可以用来支持在调试器下运行测试。
-
第二组由测试实现用于检查条件和报告故障
方法 检查 assertEqual(a, b)
a == b
assertNotEqual(a, b)
a != b
assertTrue(x)
bool(x) is True
assertFalse(x)
bool(x) is False
assertIs(a, b)
a is b
assertIsNot(a, b)
a is not b
assertIsNone(x)
x is None
assertIsNotNone(x)
x is not None
assertIn(a, b)
a in b
assertNotIn(a, b)
a not in b
assertIsInstance(a, b)
isinstance(a, b)
assertNotIsInstance(a, b)
not isinstance(a, b)
还有一些不常用的断言方法可以参考官网地址官网
-
第三组,一些查询方法允许收集有关测试本身的信息,简单介绍四个,还有一些不常用的,例如测试数据清理(针对
setUp
和tearDown
执行失败的情况)、协程。-
fail(msg=None)
:指定断言失败的错误信息msg
def fail(self,msg="test fail cause it has some problems"): print("用例执行错误信息:{}".format(msg))
this is a test_upper method 用例执行错误信息:'FOO' != 'O' - FOO + O
-
id()
:获取测试方法的全名,包括模块和类名import unittest class Test_id(unittest.TestCase): def test_split(self): print(self.id()) print("this is a test_split method") s = 'hello world' self.assertEqual(s.split(s),['hello','world']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) if __name__ == "__main__": unittest.main()
-
defaultTestResult()
:返回应用于此测试用例的测试结果类的实例(如果未向该run()
方法提供其他结果实例)。 -
shortDescription()
:返回测试的描述,或者None
没有提供描述。此方法的默认实现返回测试方法doc string
的第一行(如果有)def test_isupper(self): """ 这是一个描述 :return: """ print("this is a test_isupper method") print(self.shortDescription()) self.assertTrue("FOO".isupper()) self.assertFalse('Foo'.isupper())
Ran 1 test in 0.002s OK this is a test_isupper method 这是一个描述
-
8.2 TestSuite(测试套件)
class unittest.TestSuite(tests=())
此类表示各个测试用例和测试套件的集合。该类提供测试运行程序所需的接口,以使其能够像其他任何测试用例一样运行。运行TestSuite
实例与遍历套件(分别运行每个测试)的结果相同。
TestSuite
对象非常相似,不同之处在于它们实际上并未实施测试。相反,它们用于将测试聚合到应一起运行的测试组中。可以使用一些其他方法将测试添加到TestSuite
实例:
-
addTest(test)
:将TestCase
或TestSuite
添加到套件中# test_addTest.py import unittest from test_demo01 import TestStringMethods def suite(): suite = unittest.TestSuite() suite.addTest(TestStringMethods('test_upper')) suite.addTest(TestStringMethods('test_isupper')) return suite if __name__ == '__main__': runner = unittest.TextTestRunner() runner.run(suite())
-
addTests(test)
:将来自所有可迭代的TestCase
和TestSuite
实例的测试添加到该测试套件中。这等效于遍历测试,调用addTest()
每个元素:# test_addTests.py import unittest from test_demo01 import TestStringMethods def suite(): suite = unittest.TestSuite() suite.addTests([TestStringMethods('test_upper'),TestStringMethods('test_isupper')]) return suite if __name__ == '__main__': runner = unittest.TextTestRunner() runner.run(suite())
TestSuite
和TestCase
类都拥有下面的方法:
-
run(result)
:运行与此套件相关的测试,从结果对象中收集结果。请注意,不同于TestCase.run()
,TestSuite.run()
需要传递结果对象# test_run.py import unittest from test_demo01 import TestStringMethods def suite(): resObj = unittest.TestResult() suite = unittest.TestSuite() suite.addTests([TestStringMethods('test_upper'),TestStringMethods('test_isupper')]) result = suite.run(resObj) return result if __name__ == '__main__': print(suite())
# 运行结果 <unittest.result.TestResult run=2 errors=0 failures=1>
-
debug()
:运行与此套件相关的测试,而不收集结果。这允许将测试引发的异常传播到调用方,并可用于支持在调试器下运行测试# test_debug.py import unittest from test_demo01 import TestStringMethods def suite(): suite = unittest.TestSuite() suite.addTests([TestStringMethods('test_upper'),TestStringMethods('test_isupper')]) suite.debug() if __name__ == '__main__': suite()
-
countTestCases()
:返回此测试对象表示的测试数量,包括所有单个测试和子套件# test_countTestCases.py import unittest from test_demo01 import TestStringMethods def suite(): suite = unittest.TestSuite() suite.addTests([TestStringMethods('test_upper'),TestStringMethods('test_isupper')]) print(suite.countTestCases()) if __name__ == '__main__': suite()
# 执行结果 2
TestLoader()使用
TestLoader
类被用来创建测试套件和测试模块,使用时,通常不需要创建实例,unittest
模块提供了一个实例可以被共享的实例,unittest.defautlTestLoader
,如果使用子类和实例可以自定义一些可配置的属性。其可用来加载TestCase
到TestSuite
中,即加载满足条件的测试用例,并把测试用例封装成测试套件。使用unittest.TestLoader
,通过该类下面的discover()
方法自动搜索指定目录下指定开头的.py
文件,并将查找到的测试用例组装到测试套件
loadTestsFromTestCase:通过类名加载类下所有测试用例到测试套件中
suite = unittest.TestLoader().laodTestsFromTestCase(ClassName)
# 实例
import unittest
from test_demo01 import TestStringMethods
def suite():
suite = unittest.TestLoader().loadTestsFromTestCase(TestStringMethods)
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
loadTestsFromModule:通过模块名添加模块下加载到测试套件中
suite = unittest.TestLoader().loadTestsFromModule(ModuleName)
# 实例
import unittest
import test_demo01
def suite():
suite = unittest.TestLoader().loadTestsFromModule(test_demo01)
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
loadTestsFromName
suite = unittest.TestLoader().loadTestsFromName(name,module=None)
# name是一个string,格式为"module.class.method"
# 当module参数存在时:
# name可为"class":执行某个类的所有测试方法
# name可为"class.method":执行某个类的测试方法
# 当module参数不存在时:
# name可为"module.class.method":执行某个测试类的具体测试方法
# name可为"module.class":执行某个测试类的所有方法
# name可为"module":执行某个测试模块的所有测试类的测试方法
# 实例
import unittest
import test_demo01
def suite():
# suite = unittest.TestLoader().loadTestsFromName('TestStringMethods', test_demo01)
# suite = unittest.TestLoader().loadTestsFromName('TestStringMethods.test_upper', test_demo01)
# suite = unittest.TestLoader().loadTestsFromName('test_demo01.TestStringMethods.test_upper')
# suite = unittest.TestLoader().loadTestsFromName('test_demo01.TestStringMethods')
suite = unittest.TestLoader().loadTestsFromName('test_demo01')
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
loadTestsFromNames
suite = unittest.TestLoader().loadTestsFromNames(names, module=None)
# 实例
import unittest
import test_demo01
import test_demo02
def suite():
# 加载模块执行该模块下所有的测试类中测试方法
# suite = unittest.TestLoader().loadTestsFromNames(['test_demo01','test_demo02'])
# 加载模块中的测试类执行该测试类中的所有测试方法
# suite = unittest.TestLoader().loadTestsFromNames(['test_demo01.TestStringMethods', 'test_demo02.DefaultWidgetSizeTestCase'])
# 加载某个测试类的具体的测试方法
suite = unittest.TestLoader().loadTestsFromNames(['test_demo01.TestStringMethods.test_upper', 'test_demo02.DefaultWidgetSizeTestCase.test_default_widget_size'])
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
discover:自动搜索指定目录指定开头的.py
文件,并将查找到的测试用例组装到测试套件
suite = unittest.defaultTestLoader.discover(test_dir,pattern="test*.py")
# 实例
import unittest
import test_demo01
import test_demo02
def suite():
suite = unittest.defaultTestLoader.discover("./", "my*.py")
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
BeautifulReport
利用BeautifulReport
可生成HTML格式的测试报告。
安装
pip install beautifulreport
BeautifulReport
使用
import unittest
from test_demo01 import TestStringMethods
from BeautifulReport import BeautifulReport
def suite():
suite = unittest.TestSuite()
suite.addTest(TestStringMethods('test_upper'))
suite.addTest(TestStringMethods('test_isupper'))
return suite
if __name__ == '__main__':
# runner = unittest.TextTestRunner()
# runner.run(suite())
pr = BeautifulReport(suite())
pr.report(filename="test_report.html",description="测试报告1.0",report_dir="report/")
# 输出
.F
测试已全部完成, 可打开 D:\Code\PycharmProjects\UnitTest\report\test_report.html 查看报告
HwTestReport使用
HwTestReport
是HTMLTESTRunner
的强化版,内容比其更丰富些。
项目地址:https://github.com/hongweifuture/HwTestReport,下载HwTestReport.py
文件,放在python目录的lib文件夹下
使用
import unittest
from test_demo01 import TestStringMethods
from BeautifulReport import BeautifulReport
# 如果需要英文版就导入HTMLTestReportEN
# from HwTestReport import HTMLTestReportEN
from HwTestReport import HTMLTestReport
def suite():
suite = unittest.TestSuite()
suite.addTest(TestStringMethods('test_upper'))
suite.addTest(TestStringMethods('test_isupper'))
return suite
if __name__ == '__main__':
with open('report/test_report.html', 'wb') as report:
runner = HTMLTestReport(stream=report,
verbosity=2,
images=True,
title='HwTestReport 测试',
description='带截图,带饼图,带详情',
tester='Hincy')
runner.run(suite())
# 输出
ok test_upper (test_demo01.TestStringMethods)
F test_isupper (test_demo01.TestStringMethods)
Time Elapsed: 0:00:00