pyunit中文版
——基于python3.6.6rc1
1. unittest框架
unittest的设计灵感最初来源于Junit以及其他语言中具有共同特征的单元框架。它支持自动化测试,在测试中使用setup(初始化)和shutdown(关闭销毁)操作,组织测试用例为套件(批量运行),以及把测试和报告独立开来。
为了实现这些,unittest以一种面向对象的方式产生了一些很重要的概念
-
test fixture
fixture表示tests运行前需要做的准备工作以及结束后的清理工作。比如,创建临时/代理数据库、目录或启动一个服务器进程。 -
test case
test case是单元测试中的最小个体。它检查特定输入的响应信息。unittest提供了一个基础类:TestCase,用来创建test case。 -
test suite
test suite是test case的合集,通常用test suite将test case汇总然后一起执行。 -
test runner
test runner是一个执行器,它可以执行case并提供结果给用户。它可以提供图形界面、文本界面或者返回一个值表示测试结果。
1.1. 示例
unittest模块提供了丰富的工具,用于组织和运行tests。本节演示了其中一小部分功能,但足以满足大多数用户的需要。
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', '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()
使用unittest.TestCase的子类创建了一个case。其中有三个test开头的测试方法,这种命名方式可以告诉执行器(test runner)哪些方法可以被执行。也就是说test开头的测试方法才会被执行。
这些测试方法中都使用了assertEqual()来判断预期值,使用assertTrue()和assertFalse()来验证条件,或使用assertRaises()来验证某个具体的被抛出的异常。这些断言方法经常被用来替代assert,这样执行完成后,所有的测试结果都会被记录并写进报告。
setUp()和tearDown()方法可以实现在测试运行前后做一些其他工作,具体使用情况下面提出。
最后这部分展示了一个最简单的执行测试的方式。unittest.main()提供了一个命令行接口,当使用命令行运行测试完成后,大致会打印如下信息:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
使用-v参数将可以显示更多信息。如下:
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
上面的例子展示了unittest中最常用的功能特征,这些足以应付平时的需要。文档的剩余部分将从头开始讲解所有unittest的内容。
1.2. 命令行
unittest模块支持从命令行运行模块中的测试、类,甚至是单独的测试方法。
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
你也可以使用文件名代替测试模块名。指定的文件必须是可导入的。路径就是去掉“.py”后的文件名,文件名和模块名之间使用“.”连接。如果你要执行的测试文件不能被作为模块导入,也可以直接执行该文件。
通过传入-v选项,你可以查看更多的执行细节:
python -m unittest -v test_module
当命令行中不传入任何执行目标时,默认执行Test Discovery中的测试:
python -m unittest
查看所有命令参数:
python -m unittest -h
该功是能在3.2版本中新增的。早期的版本只能运行单独的测试方法,不能运行模块和测试类。
1.2.1. 命令行选项
unittest支持以下命令行选项:
- -b,–buffer 当测试在运行时,输出的错误信息被缓存在内存中,如果测试运行通过,则释放缓存,如果测试失败,信息就会被打印出来。
- -c,–catch 在测试执行时按下Control-C将会等待当前测试完成并输入所有测试报告。第二次按下Control-C就会触发KeyboardInterrupt异常。
- -f,–failfast 测试一旦出现错误或失败就停止运行。
- –locals 在异常回溯中打印本地变量信息。
命令行选项-b、-c和-f在3.2版本中新增。
命令行选项–locals在3.5版本中新增。
命令行选项也可以在Test Discovery中使用,所有项目中的测试都可以使用。
1.3. Test Discovery
3.2版本中新增。
unittest支持简单的测试发现功能。为了兼容test discovery,所有的测试文件必须是能从项目的顶层目录导入的模块或包(包括命名空间包),意思就是文件名必须是有效的标识符。
Test Discovery在TestLoader.discover()中实现,但是仍能用于命令行模式。基本使用方法如下:
cd project_directory
python -m unittest discover
注意:python -m unittest虽然作用和python -m unittest discovery一样。但是如果要传递参数给discover的话还是要写完整的命令。
选项:
-
-v,–verbose 详细的输出
-
-s,–start-directory 目录 Discovery寻找的其实目录(默认.)
-
-p,–pattern 模式 匹配的测试文件的格式(默认test*.py)
-
-t,–top-level-directory 目录 项目的顶级目录(directory的默认起始目录)
-
-s,-p和-t选项可以作为位置参数,以下两个命令是等效的:
python -m unittest discover -s project_directory -p "*_test.py"
python -m unittest discover project_directory "*_test.py"
可以使用传递一个包名路径,比如 myproject.subpackage.test作为初始目录。这样的话,这个包就会被导入,包的路径就会被当做初始目录。
注意:Discovery通过导入的方式来加载测试。一旦test discovery从你提供的初始目录中发现了所有的测试文件,它就会将路径转换成包名导入,比如foo/bar/baz.py会被当做foo.bar.baz进行导入。
如果你有一个在公共的包并且使用test discovery去加载该包的另一个不同副本,那么就会引发异常,导致discovery退出。
如果你提供了一个起始目录作为包名称而不是提供一个路径作为目录,那么discovery就会认为所有位置的文件都是你需要导入的(包括中间路径),这样就不会报错。
测试模块和包通过load_tests协议以指定的方式加载测试和运行discover。
3.4版本中修改:Test discovery支持命名空间包。
1.4. 组织测试代码
单元测试中最基本的组成是test cases——单一的测试逻辑场景以及正确性校验。在unittest中,test cases被unittest.TestCae的实例替代。要编写你自己的测试用例就必须写一个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实例被当做Test Fixture创建,用于执行每个测试方法。也就是说,TestCase.setUp、TestCase.tearDown和TestCase.__init__会在每个用例执行前运行一次。
建议使用TestCase去根据测试的特性对其进行分组。对此,unittest提供了一个机制:测试套件(test suit),由unittest的TestSuit类实现。如果测试很多的话,调用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())
你可以将测试用例或者测试套件写在被测试的代码中,但是并不建议这么做。应该将测试部分单独写在一个文件,这么做有以下好处:
- 测试模块可以单独在命令行运行
- 测试代码部分可以更容易的单独拿出来
- 不会为了迎合被测试的代码而随意修改测试代码
- 测试代码会更少的修改
- 被测试代码重构更容易
- 用C写的模块测试的代码必须是单独的模块,那么为什么不保持一直呢? 如果测试策略改变了,不需要去修改源码
1.5. 使用旧的测试代码
一些使用者会发现现存的一些旧的测试代码并不是TestCase的子类,但是仍需要执行。
基于此,unittest提供了FunctionTestCase类。TestCase的子类可以直接包含这些旧的测试类,并且同样可以实现用例的setUp()初始化和tearDown()销毁操作。
例如:
def testSomething():
something = makeSomething()
assert something.name is not None
#...
此时可以创建一个等效的测试用例如下,包含set-up和tear-down方法:
testcase = unittest.FunctionTestCase(testSomething,setUp=makeSomethingDB,tearDown=deleteSomethingDB)
注意:虽然FunctionTestCase可以很快的将普通测试方法转换成单元测试,但一般不建议这么做。尽量花时间去构造TestCase的子类作为测试将会使的以后的代码重构变得容易。
有时候,已有的测试是使用doctest写的。如果这样的话,doctest提供了一个DocTestSuites类,可以自动的将该测试转换成unittest.TestSuite实例。
1.6. 跳过失败的测试
版本3.1中新增。
Unittest支持跳过单个测试以及整个测试类。此外,还支持将一个测试标记为“预计失败”,表示该测试有问题,但是这个失败不会在TestResult中记录。
跳过一个测试很简单,只需使用skip()或者附带一些条件判断的skip()。
一般的跳过是这样的:
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
以下是运行的结果输出:
test_format (__main__.MyTestCase) ... skipped 'not supported in this library version'
test_nothing (__main__.MyTestCase) ... skipped 'demonstrating skipping'
test_windows_support (__main__.MyTestCase) ... skipped 'requires Windows'
----------------------------------------------------------------------
Ran 3 tests in 0.005s
OK (skipped=3)
类的跳过和方法一样:
@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
def test_not_run(self):
pass
TestCase.setUp()也可以设置跳过测试,这在将一些资源设置为不可用时非常实用。
对于预期的失败使用exceptedFailture()装饰器:
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))
以下这些装饰器实现了跳过测试以及预期失败的功能:
@unittest.skip(reason)
直接跳过被装饰的测试,reason是描述跳过的原因。
@unittest.skipIf(condition, reason)
如果if的条件成立则跳过测试。
@unittest.skipUnless(condition, reason)
除非条件为真,否则跳过测试。
@unittest.expectedFailure
标记测试为失败,如果在运行时失败,则不会在结果中统计。
exception unittest.SkipTest(reason)
跳过测试抛出的异常。
通常你可以使用TestCase.skipTest()或者上述装饰器中的某一个来替代这种抛异常的做法。如果测试被跳过,则不会执行setUp()和tearDown()方法;如果类被跳过,则不会执行setUpClass()和tearDownClass()方法;如果模块被跳过,则不会执行setUpModule()和tearDownModule()方法。
1.7. 使用subTest()
版本3.4中新增。
当部分测试只有细微的差别时,比如参数,unittest支持直接在测试方法内部使用subTest()上下文管理器来实现:
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)
输出为:
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
但是如果不使用subTest()的话,测试会在第一次失败时就退出,并且错误信息不是很明白,比如该例中的i就无法识别:
======================================================================
FAIL: test_even (__main__.NumbersTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
1.8. 类和方法
这个章节将深度讲解unittest的API部分。
1.8.1. Test Cases
unittest.TestCase(methodName=’runTest’)类
在unittest系统中,TestCase的实例就是逻辑测试单元。该类作为一个基类,其特性和功能通过创建子类类得以实现。类内部实现了一些接口,可以让test runner驱动测试,以及测试中可以使用一些方法来断言和报告各种失败情况。
每一个TestCase的实例运行时都是运行一个方法:该方法名就是methodName。在大多数情况下,你都不需要修改methodName,也不需要重新实现runTest()方法。
版本3.2中修改内容:TestCase可以不需要methodName就进行实例化,这使得TestCase在交互式平台上更容易使用。
TestCase的实例的方法可以分为3种:一种是用来执行测试的;一种是用来实现条件断言和失败报告的;还有一部分是用来收集测试本身的一些信息。
第一种方法大致有:
- setUp()
该方法负责Test Fixture中的准备工作,一般在测试执行前调用;除了AssertionError和SkipTest以外,该方法触发的其他异常均被是做错误处理而不是测试失败,该方法默认没有操作。
- tearDown()
该方法在测试方法被执行并且结果记录后立即被调用。如果测试方法出现异常,子类实现的功能就需要特别的检查。任何异常,除了AssertionError和SkipTest,只要是该方法抛出的异常都会被视为其他错误而不是测试失败(会在报告中统计)。该方法只有在setUp()执行成功时才会调用,而跟测试的结果无关。默认该方法没有操作。
- setUpClass()
当类中的测试方法被执行前会被调用的一个类方法。该方法只会在类方法前调用,也就是带有calssmethod装饰器并且没有其他参数的方法。
@classmethod
def setUpClass(cls):
...
版本3.2中新增。
- tearDownClass()
当类测试方法被执行完后会被调用的一个类方法。该方法只会在一个类的所有方法执行完成后调用,该方法被调用时,必须有calssmethod装饰器并且除了类以外没有其他参数的方法。
@classmethod
def tearDownClass(cls):
...
版本3.2中新增。
- run(result=None)
执行测试,并收集信息作为结果传递给TestResult对象。如果结果为空,一个临时的报告文件就会被调用的defaultTestResult()方法创建并使用。然后将结果对象返回。
通过简单的调用TestCase实例可以达到相同的效果。
版本3.3中修改:早期的run版本中不会返回测试结果,也不会调用实例。
- SkipTest(reason)
当需要跳过一个测试方法时,可以在测试方法或setUp()中调用该方法。
版本3.1中新增。
- subTest(msg=None,**params)
把一个封闭的代码块当做subTest执行并返回上下文管理器。msg和params是可选的,主要用来显示信息,以方便测试执行异常时定位问题。
一个测试中可以包含任意个subTest,并且可以多层嵌套。
版本3.4中新增。
- debug()
运行测试但不生成报告。这样运行测试时会直接抛出异常,可以在调试模式下使用。
TestCase提供了很多断言方法,以下是常用的:
所有的断言方法都接收一个msg的参数,用来在测试失败时显示。注意,msg关键字参数只有在assertRaises(),assertRaisesRegex(), assertWarns(), assertWarnsRegex()方法被当做上下文管理器使用时才能被其使用。
1. assertEqual(first,second,msg=None)
测试第一个参数和第二个参数是否相等。如果比较不一致,则测试失败。
此外,如果两个参数都是同一种类型,且都是某种列表类型的话, addTypeEqualityFunc()会被调用用以显示更多的错误信息。
版本3.1修改:新增自动调用特定对比方法。
版本3.2修改:新增assertMultiLineEqual()方法作为比较字符串时调用的特定类型方法。
2. assertNotEqual(first,second,msg=None)
判断两个参数不相等。如果相等的话,则测试失败。
3. assertTrue(expr,msg=None)
4. assertFalse(expr,msg=None)
测试expr是否为真(或假)。
注意:上述方法等同于bool(expr) is True,但不等同于expr is True(或者使用assertIs(expr,True))。当有更多具体的方法可用时应当避免使用上述方法(比如,使用assertEqual(a,b)而不是assertTrue(a==b)),因为这些具体的方法会提供更多的错误失败信息。
5. assertIs(first,second,msg=None)
6. assertIsNot(first,second,msg=None)
测试第一个参数和第二个参数是否同一个对象。
版本3.1中新增。
7. assertIsNone(expr,msg=None)
8. assertIsNotNone(expr,msg=None)
测试expr是否为None。
版本3.1中新增。
9. assertIn(first,second,msg=None)
10. assertNotIn(first,second,msg=None)
测试参数一是否包含在参数二中。
版本3.1中新增。
11. assertIsInstance(obj,cls,msg=None)
12. assertNotIsInstance(obj,cls,msg=None)
测试obj是否为cls的实例(cls可以是一个类或者类组成的元组),检查具体的对象类型使用assertIs(type(obj),cls)。
版本3.2中新增。
还可以使用如下方法检查异常、警告和日志信息:
- assertRaises(exception,callable,*args, **kwds)
- assertRaises(exception,msg=None)
测试callable传入某些参数并调用时引发的异常。如果exception异常被抛出,表示测试通过。如果抛出的是另一种异常或无异常则表示测试失败。如果需要捕捉一组异常时,exception也可以是一个异常类型组成的元组。
如果参数只传递了exception参数,或者也有msg,这样就可以在with上下文管理器中测试代码块而不需要在方法中:
with self.assertRaises(SomeException):
do_something()
当使用上下文管理器时,assertRaises()接收一个额外的参数msg。
上下文管理可以将捕捉到的异常存储在exception属性中,这样当异常发生时,可以做进一步检查:
with self.assertRaises(SomeException) as cm:
do_something()
the_exception = cm.exception
self.assertEqual(the_exception.error_code, 3)
版本3.1修改:新增assertRaises()可以作为上下文管理器使用。
版本3.2修改:新增exception属性。
版本3.3修改:当作为上下文管理器使用时新增msg参数。
- assertRaisesRegex(exception,regex,callable,*args,**kwds)
- aseertRaisesRegex(exception,regex,msg=None)
类似于assertRaises(),不过这里需要匹配regex和捕捉异常的信息是否匹配。regex可以是普通的正则表达式,或是一个包含正则表达式的字符串。例如:
self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$",int, 'XYZ')
with self.assertRaisesRegex(ValueError, 'literal'):
int('XYZ')
版本3.1新增:asserRaisesRegexp。
版本3.2修改:重命名为assertRaiseRegex()。
版本3.3修改:当作为上下文管理器使用时新增msg参数。
- assertWarns(warning,callable,*args,**kwds)
- assertWarns(warning,mag=None)
当callable被传入参数调用时,测试其触发的警告。如果触发异常则表示测试成功,否则失败。如果想要测试一组警告,可以在warning出传入包含警告类的元组。
如果有了warning参数,甚至msg参数,那么此时可以直接在with代码块下测试代码,而不需要在方法中使用:
with self.assertWarns(SomeWarning):
do_something()
上下文管理器会把捕捉的警告信息存储在warning属性中,触发警告的源代码行存储在filename和lineno属性中。这些信息在触发警告后检查时很有用:
with self.assertWarns(SomeWarning) as cm:
do_something()
- self.assertIn(‘myfile.py’, cm.filename)
- self.assertEqual(320, cm.lineno)
该方法被调用时就会执行,无论警告过滤器是否就位。
版本3.2中新增。
版本3.3修改:当作为上下文管理器使用时新增msg参数。
- assertWarnsRegex(warning,regex,callable,*args,**kwds)
- assertWarnsRegex(warning,regex,msg=None)
类似于assertWarns(),不过这里还需要测试regex是否匹配警告信息。regex可以是正则表达式也可以是一个包含正则表达式的字符串:
self.assertWarnsRegex(DeprecationWarning,r'legacy_function\(\) is deprecated',legacy_function, 'XYZ')
或
with self.assertWarnsRegex(RuntimeWarning, 'unsafe frobnicating'):
frobnicate('/etc/passwd')
版本3.2中新增。
版本3.3中修改:作为上下文管理器使用时新增msg参数。
- assertLogs(logger=None,level=None)
在上下文管理器中使用,测试至少有一条信息被记录在logger中,且最低级别是level。
如果参数logger被给定,logger将是一个logging.Logger对象,或者logger的名字。默认是root记录器,会记录所有信息。
如果给定的话,level可以是数字,表示日志记录级别,也可以是字符串格式(比如“error”和logging.ERROR)。默认是logging.INFO。
在with代码块中,只要有一条信息和logger和level匹配则表示测试成功,否则失败。
上下文管理器返回的对象是一个记录器,记录了匹配的日志信息记录。包含两个属性:
- records
一个匹配日志信息的logging.LogRecord对象列表。 - output
匹配到的信息的格式化输出。
例:
with self.assertLogs('foo', level='INFO') as cm:
logging.getLogger('foo').info('first message')
logging.getLogger('foo.bar').error('second message')
self.assertEqual(cm.output, ['INFO:foo:first message','ERROR:foo.bar:second message'])
版本3.4中新增。
还有其他执行具体断言的方法:
-
assertAlmostEqual(first,second,places=7,msg=None,delta=None)
-
assertNotAlmostEqual(first,second,places=7,msg=None,delta=None)
测试参数1和参数2是否约等。小数点保留位数为places(默认7)。
如果delta被提供(替代places),表示参数1和参数2之间的差异必须小于delta。
如果places和delta同时存在时触发TypeError。
版本3.2修改:assertAlmostEqual()自动识别相似的对象进行比较。如果对象比较一致则assertAlmostEqual()自动失败。新增delta参数。
-
assertGreater(first,second,msg=None)
-
assertGreaterEqual(first,second,msg=None)
-
assertLess(first,second,msg=None)
-
assertLessEqual(first,second,msg=None)
正如方法名含义,比较参数1和参数2之间是否存在>、>=、<或者<=关系。
self.assertGreaterEqual(3, 4)
AssertionError: "3" unexpectedly not greater than or equal to "4"
版本3.1新增。
- assertRegex(text,regex,msg=None)
- assertNotRegex(text,regex,msg=None)
测试regex是否匹配text。如果匹配失败,错误信息会包含匹配模式和text(或者部分匹配不成功的text)。regex为一般的正则表达式或者可以被re.search()识别的其他字符串格式。
版本3.1新增:命名为assertRegexpMatches。
版本3.2修改:重命名为assertRegex()。
版本3.2新增:assertNotRegex()。
版本3.5新增:assertNotRegexpMatches被弃用。
- assertCountEqual(first,second,msg=None)
测试序列first中的元素是否和second中的一致,不考虑排序。如果不同,会生成详细的错误清单。
在比较时,重复的元素不会被忽略。等效于:assertEqual(Counter(list(first)),Counter(list(second))),只适用于序列。
版本3.2新增。
assertEqual()方法为同类型对象的比较提供了多种具体的比较方法。这些方法大多数都已经在内置方法实现,但是你仍可以使用addTypeEqualityFunction()方法新注册一个方法:
- addTypeEqualityFunc(typeobj,function)
注册一个被assertEqual()调用的特定类型的方法,用以判断两个相同类型的对象(不是子类)是否相等。function必须有2个形参,还有一个msg=None参数,和assertEqual()中使用方法一样。当2个参数比较第一次出现不相等时就引发self.failureException(msg)异常,这可能会提供一些有用的信息,并在错误信息中解释哪里不同。
版本3.1新增。
assertEqual()方法会自动的调用下面列表中的方法。但是通常没有必要直接调用这些方法:
- assertMultiLineEqual(first,second,msg=None)
测试多行字符串first是否等同于字符串second。当不相同时,不一致的地方会在错误信息中高亮显示。使用assertEqual()方法比较字符串时,这个方法会被默认调用。
版本3.1新增。
- assertSequenceEqual(first,second,msg=None,seq_type=None)
测试两个序列是否相等。如果seq_type被提供,参数1和参数2都必须是seq_type的实例,否则引发异常。如果两个序列不等,会在错误信息中显示不同。
版本3.1新增。
- assertListEqual(first,second,msg=None)
- assertTupleEqual(first,second,msg=None)
测试列表或者元组是否相等。如果不同,会打印错误信息并显示不同之处。当参数类型错误时仍会报错。这些方法通常是在比较列表和元组时被assertEqual()默认调用。
- assertSetEqual(first,second,msg=None)
测试集合是否相等。如果不同,会打印错误信息并显示不同之处。该方法通常是在比较集合和frozensets时被assertEqual()默认调用。
版本3.1新增。
- assertDictEqual(first,second,msg=None)
测试字典是否相等。如果不同,会打印错误信息并显示不同之处。该方法通常是在比较字典时被assertEqual()默认调用。
版本3.1新增。
最后,TestCase提供了一下方法和属性:
fail(msg=None)
当某个测试失败时,可以将msg提供给错误信息。
failureException
该类属性提供了测试方法抛出的异常。如果测试框架需要一个特定的异常,比如携带更多的信息,就必须在子类中实现该异常。该属性的初始值为AssertionError。
longMessage
这个类属性决定了当一个自定义失败信息被当做msg参数传递给assertXYY等方法时该做什么。默认值是True。这种情况下,自定义的信息被添加到标准失败信息后面。当值为False时,自定义信息会覆盖标准信息。
在每个测试方法中,可以重写该属性:调用assert方法之前,分配实例属性self.longMessage的值为True或False。
每次测试运行前该设置都会被重置。
版本3.1新增。
maxDiff
该属性决定了assert方法结果中差异信息的显示长度。默认有80*8个字符。受此影响的方法有assertSequenceEqual()(包括所有序列的比较方法),assertDictEqual()和assertmultiLineEqual()。
如果maxDiff设置为None时,表示没有最大长度。
版本3.2新增。
测试框架可以使用如下方法在测试中收集信息:
countTestCases()
通过测试对象返回测试的数量。对于TestCase的实例来说,这个值是1.
defaultTestResult()
返回一个测试报告类的实例,用于test case类(如果没有提供其他报告实例给run()方法)。
id()
返回用例的id。通常是测试方法的全名,包括模块名和类名。
shortDescription()
返回测试的描述信息,如果没有则返回None。该方法默认会返回方法docstring信息的第一行,如果不可用则返回None。
版本3.1修改:在3.1中添加了测试的名称在描述中。这个特性与unittest的扩展存在冲突,于是在python3.2中增加测试名称被转移到了TextTestResult。
**addCleanup(function,*args,**kwargs)
在tearDown()方法之后新增调用function方法去清理测试时使用的资源。方法会根据添加的反序进行调用(LIFO)。当他们被调用时,可以接收任何传递给addCleanUp()的参数。
如果setUp()失败,那么tearDown()就不会执行。但是cleanup方法仍会被执行。
版本3.1新增。
doCleanups()
该方法会在tearDown()之后被调用,或者当setUp()失败时。
该方法负责调用所有添加在addCleanup()中的清理方法。如果你需要清理在tearDown()中被调用的方法,那么你需要手动调用doCleanup()方法。
doCleanup()将方法从清理方法栈中取出,所以它可以在任何时间调用。
版本3.1新增。
class unittest.FunctionTestCase(testFunc,setUp=None,tearDown=None,description=None)
该类实现了TestCase接口即允许test runner操纵测试,但是没有提供测试代码检查和报告错误的方法。这通常被用来将剩余的测试代码创建为测试用例,并且可以集成到unittest测试框架中。
1.8.2. 弃用的方法
由于一些历史的原因,一些TestCase的方法别名被弃用。以下列表是这些方法的正确名字和被弃用的名字:
1.9. 组织用例
- class unittest.TestSuite(tests=())
这个类代表一个测试用例或测试套件的集合。该类描述了一个可以被test runner执行的接口,通过它可以执行任何测试。运行一个TestSuite就相当于将测试套件迭代,然后执行每一个测试。
如果tests被给与,那么它必须是一个可迭代对象,用来创建套件。稍后会介绍一些添加测试或测试套件到测试集合中的一些方法。
TestSuite对象和TestCase对象比较类似,除了不会执行测试以外。相反,TestSuite经常用来吧测试用例组织在一起,使测试可以一起执行。一些方法可以用来将测试添加到TestSuite中去:
- addTest(test)
添加一个TestCase或TestSuite到套件中。
- addTests(tests)
把TestCase和TestSuite中给的所有的测试实例添加到套件中。
等价于迭代所有的测试,然后使用addTest()逐个添加。
TestSuite和TestCase都有如下方法:
- run(result)
运行测试套件,并收集结果信息传递给测试报告对象。注意不同于TestCase.run(),TestSuite.run()需要传递result对象。
- debug()
运行测试套件但不收集测试结果。允许异常抛出并传递给上层调用者。经常用在调试模式下。
- countTestCases()
返回测试数量,包括所有的单独测试和部分测试套件。
- iter()
用例通常通过TestSuite的迭代访问。子类可以通过重写__iter__()来提供用例。注意这个方法可能会在一个套件被调用好多次(比如计数或者比较),所以在TestSuite.run()之前每次迭代返回的用例都是一样的。在TestSuite.run()之后,调用者就不再依赖该方法返回的用例,除非调用者使用子类重写了TestSuite._removeTestAtIndex()以保存用例的引用。
1.9.1. 加载和执行用例
- class unittest.TestLoader
TestLoader经常用来从类和模块中提取创建测试套件。一般情况下TestLoader不需要实例化,unittest内部提供了一个实例可以当做unittest.defaultTestLoader使用。无论使用子类还是实例,都允许自定义一些配置属性。
TestLoader对象具有以下属性:
errors
加载用例时遇到的非致命错误信息列表。不会被重置。致命的错误会被相关方法直接抛异常给调用者。非致命错误也会在综合测试运行时的原始错误中被抛出。
版本3.5新增。
TestLoader对象具有以下方法:
- loadTestsFromTestCase(testCaseClass)
从TestCase派生类testCaseClass中加载所有测试用例并返回测试套件。
getTestCaseNames()会从每一个方法中创建Test Case实例,这些方法默认都是test开头命名。如果getTestCaseNames()没有返回任何方法,但是runTest()方法被实现了,那么一个test case就会被创建以替代该方法。
- loadTestsFromModule(module,pattern=None)
从模块中加载所有测试用例,返回一个测试套件。该方法会从module中查找TestCase的派生类并为这些类里面的测试方法创建实例。
注意:当使用TestCase派生类时可以共享test fixtures和一些辅助方法。该方法不支持基类中的测试方法。但是这么做是有好处的,比如当子类的fixtures不一样时。
如果一个模块提供了load_tests方法,它会在加载测试时被调用。这样可以自定义模块的测试加载方式。这就是load_tests协议。pattern作为第三个参数被传递给load_tests。
- loadTestsFromName(name,module=None)
从一个字符串中加载测试并返回测试套件。
字符串说明符name是一个虚名(测试方法名的字符串格式),可以适用于模块、测试类、test case类中的测试方法、TestSuite实例或者一个返回TestCase或TestSuite的可调用的对象。并且这样的话,在一个测试用例类中的方法会被当做‘测试用例类中给的测试方法’而不是‘可调用对象’。
比如,你有一个SampleTests模块,里面有一个TestCase的派生类SampleTestCase,类中有3个方法(test_one(),test_two(),test_three()),使用说明符“SampleTests.SampleTestCase”就会返回一个测试套件,里面包含所有3个测试方法。如果使用“SampleTests.SampleTestCase.test_two”就会返回一个测试套件,里面只包含test_two()测试方法。如果说明符中包含的模块或包没有事先导入,那么在使用时会被顺带导入。
- loadTestsFromNames(names,module=None)
使用方法和loadTestsFromName(name,module=None)一样,不同的是它可以接收一个说明符列表而不是一个,返回一个测试套件,包含所有说明符中的所有测试用例。
- getTestCaseName(testCaseClass)
返回一个有序的包含在TestCaseClass中的方法名列表。可以看做TestCase的子类。
- discover(start_dir,pattern=’test*.py’,top_level_dir=None)
从指定的start_dir(起始目录)递归查找所有子目录下的测试模块,并返回一个TestSuite对象。只有符合pattern模式匹配的测试文件才会被加载。模块名称必须有效才能被加载。
顶级项目中的所有模块必须是可导入的。如果start_dir不是顶级路径,那么顶级路径必须单独指出。
如果导入一个模块失败,比如由于语法错误,会被记录为一个单独的错误,然后discovery会继续。如果导入失败是由于设置了SkipTest,那么就会被记录为忽略测试。
当一个包(包含__init__.py文件的目录)被发现时,这个包会被load_tests方法检查。通过package.load_tests(loader,tests,pattern)方式调用。Test Discovery始终确保包在调用时只进行一次测试检查,即使load_tests方法自己调用了loader.discover。
如果load_tests方法存在,那么discovery就不再递归,load_tests会确保加载完所有的当前包下的测试。
pattern没有被特意存储为loader的属性,这样包就可以自行查找。top_level_dir被存储,所以load_tests就不需要再传参给loader.discover()。
start_dir可以是一个模块名,也可以是一个目录。
以下这些TestLoader的属性可以在子类和实例中配置:
- testMethodPrefix
一种字符串,放在方法的名字前面时,该方法会被当做测试方法。默认一般是‘test’。
在getTestCaseNames()和所有的loadTestsFrom*()方法中都有效。
- sortTestMethodUsing
在getTestCaseNames()和所有的loadTestFrom*()方法中对测试方法进行排序时对测试方法名进行比较的函数。
- suiteClass
从一个测试序列中构造出一个测试套件的可调用对象。生成的对象没有方法。默认值是TestSuite类。
对所有的loadTestsFrom*()方法有效。
- class unittest.TestResult
该类被用来整理测试报告。
一个TestResult对象保存了一系列测试的结果。TestCase和TestSuite类确保了结果会被正确的记录。测试人员不需要担心测试的输出问题。
基于unittest的测试框架通过运行一系列测试生成的TestResult对象来实现测试报告的目的。TestRunner.run()方法会返回一个TestResult对象来达到这个目的。
TestResult对象具有以下属性,在检查测试结果时很有用:
- errors
一个错误列表,包含了TestCase实例和异常回溯的二元组。每个元组都表示测试中出现的异常。
- failures
一个列表,包含了TestCase实例和异常回溯的二元组。每个元组都表示那些使用TestCase.assert*()导致失败的测试。
- skipped
一个列表,包含了TestCase实例和忽略测试原因的二元组。
expectedFailures
一个列表,包含了TestCase实例和异常回溯的二元组。每个元组都代表一个预期的异常。
- unexpectedSuccesses
一个列表,包含了被标记为预期失败的TestCase实例,但是却成功了。
- shouldStop
当一个测试执行应该通过stop()终止时,值设置为True。
- testsRun
到目前为止的总运行次数。
- buffer
设置为True时,sys.stdout和sys.stderr会在startTest()和stopTest()方法调用之间被缓存。如果测试失败或错误,那么输出的内容就是真实的sys.stdout或者sys.stderr。其他输出的内容会追加在失败/错误信息的后面。
- failfast
一旦设置为 True,测试在第一次出现失败或错误时就会调用stop()方法终止。
- tb_locals
如果设置为True,本地变量信息会打印在异常回溯信息中。
- wasSuccessful()
如果所有的测试都运行通过,则返回True,否则返回False。
- stop()
该方法被调用时通过设置shouldStop属性为True,从而终止运行测试。TestRunner对象一旦遇到该方法就不会继续执行测试而直接返回。
比如,这个功能可以被TextTestRunner类用来终止测试框架,当用户在键盘输入中断信号时。在交互式环境下TestRunner可以使用类似的操作。
以下这些TestResult类的方法主要用来维护内部数据结构。也可以被子类扩展用以支持额外的报告需要。在构建工具时非常有用,可以支持交互式报告,当测试在执行时:
- startTest(test)
但测试用例执行时调用。
- stopTest(test)
当测试执行完成后调用,不管结果如何。
- startTestRun()
在任何测试执行前调用一次。
- stopTestRun()
在所有测试执行完成后调用一次。
- addError(test,err)
当测试抛出异常时被调用。err是一个被sys.exc_info()返回的元组:(type,value,traceback)。
默认的会给实例的errors属性添加一个元组(test,formatted_err),其中formatted_err的格式化信息来自err。
- addFailure(test,err)
当测试失败时调用。err是sys.exc_info()返回的一个元组:(type,value,traceback)。
默认的会给实例的failures属性添加一个元组(test,formatted_err),其中formatted_err的格式化信息来自err。
- addSuccess(test)
当测试通过时调用。默认无实现。
- addSkip(test,reason)
当测试test被跳过时调用。reason是测试跳过的原因。
默认实现是给实例的skipped属性添加了元组(test,reason)。
- addExpectedFailure(test,err)
当测试失败时调用,但是是被expectedFailure()装饰器装饰的测试。
默认的实现是给实例的expectedFailures属性添加一个元组(test,formatted_err),其中formatted_err的格式化信息来自err。
- addUnexpectedSuccess(test)
在被expectedFailure()装饰的测试test执行成功时调用。默认实现是给实例的unexpectedSuccess属性添加test。
- addSubTest(test,subtest,outcome)
当subtest结束时调用。test是测试方法对应的测试用例。subtest是一个自定义的TestCase实例。
如果outcome为None,subtest成功。否则,测试失败并抛出异常,outcome为sys.exc_info返回的元组:(tyep,calue,traceback)。
默认的实现是,如果测试成功则不做任何操作,如果失败则记录subtest失败为正常的失败。
- class unittest.TextTestResult(stream,descriptions,verbosity)
一个TestResult实现的功能,被TextTestRunner使用。
- unittest.defaultTestLoader
TestLoader类的实例,主要为了共享。如果不需要自定义的TestLoader,这个实例就会被使用,而不会重新创建实例。
- class unittest.TextTestRunner(stream=None,description=True,verbosity=1,failfast=False,buffer=False,resultclass=None,warning=None,*,tb_locals=False)
一个基础的测试执行器,实现了将结果输出为流的功能。如果stream为None,默认的,sys.stderr会被作为输出流。这个类有一些可配置参数,但非常简单。运行测试套件的图形程序应该提供一些功能,这些功能可以接收**kwargs参数作为修改runners的接口,当在unittest中使用时。
默认情况下该执行器会显示 DeprecationWarning, PendingDeprecationWarning, ResourceWarning和ImportWarning,即使他们默认是忽略的。弃用警告被deprecated unittest方法引发,如果警告的过滤器是‘default’或者‘always’时,该警告只会在每个模块中出现一次,为了避免出现太多的警告信息。可以通过python的 -Wd或-Wa选项重新设置(查看Warning控制)。
- _makeResult()
该方法返回一个TestResult的实例,通过run()方法。它不能被直接调用,但是可以通过在子类中重写类实现自定义TestResult。
_makeResult()实例化类或者可调用的传递给TextTestRunner构造函数的参数resultclass。如果没有提供resultclass的话,默认值是TextTestResult。resultclass通过以下参数实例化:
stream, descriptions, verbosity
- run(test)
该方法是TextTestRunner主要的公共接口。该方法需要一个TestSuite或者TestCase实例作为参数。当调用_makeResult()时,TestResult对象被创建,,测试会被执行并且结果报告输出到stdout流中。
- unittest.main(module=’main’,defaultTest=None,argv=None,testRunner=None,testLoader=unittest.defaultTestLoader,exit=True,verbosity=1,failfast=None,catchbreak=None,buffer=None,warnings=None)
一种命令行模式下,加载和执行测试用例,这样做的主要作用是可以很简便的执行测试模块。该方法最简单的使用方式是在脚本的最后添加如下语句:
if __name__ == '__main__':
unittest.main()
通过传入verbosity参数运行测试,可以得到更多的详细信息:
if __name__ == '__main__':
unittest.main(verbosity=2)
参数defaultTest 是一个待执行的单独测试的名或者一序列的测试名(可迭代对象),如果没有通过argv参数传入指定的测试的话。如果defaultTest没有指定值或者值为None,并且参数argv没有传入值,那么所有在模块中发现的测试都会被执行。
参数argv可以传递一系列选项给程序,第一个参数默认是程序名。如果没有指定值或者值为None,那么默认会使用sys.argv的值。
参数testRunner可以是一个test runner类,也可以是一个已经创建好的test runner类的实例。通常情况下,main函数在测试执行成功或失败时调用sys.exit()作为退出。
testLoader参数必须是TestLoader的实例,默认值为defaultTestLoader。
main方法支持在交互式环境下使用,通过传入参数exit=False。以下显示没有调用sys.exit()方法的标准结果输出:
>>> from unittest import main
>>> main(module='test_module', exit=False)
failfast,catchbreak和buffer参数在命令行模式下,传入同样的选项的效果是一样。
warnings参数是一个等级过滤器,在测试执行时会用到。如果没有指定值,且命令行中传递了一个-w选项,那么该值会被锁定为None,否则该值会被设置为‘default’。
调用main方法实际上返回了一个TestProgram类的实例。保存了测试执行的结果,作为result的属性。
1.9.2. load_tests协议
版本3.2新增。
在正常执行测试时或者使用test discovery(通过实现load_tests方法),可以自定义从模块和包中加载测试用例的方式。
如果一个模块定义了load_tests方法,它会通过使用以下参数被TestLoader.loadTestsFromModule()调用:
load_tests(loader, standard_tests, pattern)
pattern参数直接从loadTestFromModule传来,默认是None。
该方法会返回一个TestSuite对象。
loader是TestLoader的实例,用来加载实例的。standard_tests是默认要从模块中加载的测试。从模块中只加载少数测试或者从测试集中去除部分测试是很常见的。第三个参数pattern就是用来加载部分测试用例的。
一个典型的load_tests方法从指定的TestCase类集合中加载测试用例的方式大概如下:
test_cases = (TestCase1, TestCase2, TestCase3)
def load_tests(loader, tests, pattern):
suite = TestSuite()
for test_class in test_cases:
tests = loader.loadTestsFromTestCase(test_class)
suite.addTests(tests)
return suite
如果discovery启动的目录下有其他包,那么该包的__init__.py会被检查,通过命令行或是调用TestLoader.discover()。如果该方法不存在,那么discovery就会遍历这个包目录。否则,discovery就会被load_tests方法代替,通过以下参数调用:
oad_tests(loader, standard_tests, pattern)
该方法会返回一个包含该包下所有测试的测试套件。standard_tests只包含从__init__.py中找到的测试。
由于传入了pattern参数给load_tests,包就会自由的查找测试。一个‘不做任何事’的load_tests方法看起来像是这样:
def load_tests(loader, standard_tests, pattern):
#top level directory cached on loader instance
this_dir = os.path.dirname(__file__)
package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
standard_tests.addTests(package_tests)
return standard_tests
2.0.类和模块的Fixtures
类和模块级别的Fixtures已经在TestSuite中被实现。当一个新的类中发现有测试时,前面的类的tearDownClass()就会被执行,然后新类的setUpClass()就会被执行。
同样的当一个模块中的测试执行完后就会执行tearDownModule()方法,而在新的模块中执行测试前会执行setUpModule()。
在所有的类或模块中的所有测试执行完成后,就会执行tearDownClass或者tearDownModule。
注意,fixtures和一些‘有潜力’的特性(比如测试并行)一起使用的话不是很好。因为它会打破测试之间的隔离。需要小心的使用。
unittest的test loader创建了默认的测试执行顺序,用以组织相同模块或类下的所有测试用例。这样的话,在每个类/模块之前都会调用一次setUpClass/setUpModule。如果你把不同模块和类的测试混在了一起,那么在执行测试时,fixture方法就可能被调用多次。
fixtures并不是为了和无序的测试套件一起工作。事实上框架中就有一个BaseTestSuite并不支持fixtures。
当fixture方法抛出异常时,当前执行的测试也会被记录为错误。因为没有对应的测试实例_ErrorHolder对象(和TestCase相同的接口)创建给对应的错误。如果你只是使用unittest的test runner功能,那么这个细节无关紧要,但如果你是一个框架设计者的话,那这个就有关系了。
2.0.1.setUpClass和tearDownClass
这两个方法必须是作为类方法使用:
import unittest
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._connection = createExpensiveConnectionObject()
@classmethod
def tearDownClass(cls):
cls._connection.destroy()
如果你想要在基类中使用setUpClass和tearDownClass,那么需要你自己去实现调用。TestCase中的实现是空的。
如果setUpClass报异常的话,那么该类的测试用例和tearDownClass都不会执行。被设置跳过的类也不会执行setUpClass和tearDownClass。如果异常是一个SkipTest异常的话,那么该类会被记录为跳过测试而不是错误。
2.0.2.setUpModule和tearDownModule
这可以被当做方法实现:
def setUpModule():
createConnection()
def tearDownModule():
closeConnection()
如果setUpModule报异常的话,那么该模块的测试用例和tearDownModule都不会执行。被设置跳过的类也不会执行setUpModule和tearDownModule。如果异常是一个SkipTest异常的话,那么该模块会被记录为跳过测试而不是错误。
2.1.信号处理
对unittest来说-c/–catch命令行选项,或者catchbreak参数对于unittest.main(),提供了更多的友好的处理control-C的方法,在测试执行时。当捕捉到control-C中断操作时,仍然允许当前测试执行完毕,并且很快执行结束并报告所有结果。捕捉到第二次control-C操作时,通常会抛出一个KeyboardInterrupt异常。
control-C操作表示处理程序尝试和代码或测试保持兼容以便于安装自己的signal.SIGINT处理程序。如果unittest的处理程序被调用,但不是已安装的signal.SIGINT处理程序的话(即在测试时它会被系统替换),那么他会调用默认的处理程序。通常情况下代码的这种行为是正常的,替换已安装的处理程序。对于单独的测试来说就需要unittest control-C操作处理被禁用的removeHandler()装饰器。
以下这些方法对框架设计者来说是实用的,去实现框架的内置control-C处理功能:
- unittest.installHandler()
安装control-C处理程序。当接收到signal.SIGINT信号时(通常是用户按下control-C键)所有的已记录的结果调用stop()方法。
- unittest.registerResult(result)
记录一个TestResult对象给control-C处理程序。记录的result存储了一个弱引用,所以它不会阻止结果被回收成垃圾。
记录一个TestResult对象任何副作用,如果control-C处理程序没有启用的话,那么测试框架就会无条件的记录所有的他们创建的结果,而不管处理程序是否弃用。
- unittest.removeResult(result)
移除记录的结果。一旦结果被移除,那么stop()就不会在result对象上调用作为control-C的响应。
- unittest.removeHandler(function=None)
当无参数调用时,该方法会移除control-C处理程序,如果它已经被安装。该方法还可以作为一个测试装饰器,当测试执行时暂时的去除处理程序。
def setUpModule():
createConnection()
def tearDownModule():
closeConnection()