import unittest
class TestStringMethods(unittest.TestCase):
@classmethod
def setUpClass(cls):
...
@classmethod
def tearDownClass(cls):
...
def setUp(self):
...
def tearDown(self):
...
def test_upper(self):
...
def test_isupper(self):
...
if __name__ == '__main__':
unittest.main(verbosity=2)
if __name__ == '__main__':
unittest.main(verbosity=2)
python tests/TestStringMethods.py
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
python -m unittest -v test_module
-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
在回溯中显示局部变量。
--durations N
显示最慢的N个测试案例。N为0时表示所有。
cd project_directory
python -m unittest discover
discover 有以下选项:
-v, --verbose
更详细地输出结果。
-s, --start-directory directory
开始进行搜索的目录(默认值为当前目录 . )
可以是路径,也可以是包名,如 myproject.subpackage.test 。包名会被导入,它在文件系统中的位置会被作为起始目录。
-p, --pattern 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"
小心 探索性测试通过导入测试对测试进行加载。在找到所有你指定的开始目录下的所有测试文件后,它把路径转换为包名并进行导入。如 foo/bar/baz.py 会被导入为 foo.bar.baz 。
如果你有一个全局安装的包,并尝试对这个包的副本进行探索性测试,可能会从错误的地方开始导入。如果出现这种情况,测试会输出警告并退出。
如果你使用包名而不是路径作为开始目录,搜索时会假定它导入的是你想要的目录,所以你不会收到警告。
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
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
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)
setUp()
为测试预备而调用的方法。 此方法会在调用测试方法之前被调用;除了 AssertionError 或 SkipTest,此方法所引发的任何异常都将被视为错误而非测试失败。 默认的实现将不做任何事情。
tearDown()
在测试方法被调用并记录结果之后立即被调用的方法。 此方法即使在测试方法引发异常时仍会被调用,因此子类中的实现将需要特别注意检查内部状态。 除 AssertionError 或 SkipTest 外,此方法所引发的任何异常都将被视为额外的错误而非测试失败(因而会增加总计错误报告数)。 此方法将只在 setUp() 成功执行时被调用,无论测试方法的结果如何。 默认的实现将不做任何事情。
setUpClass()
在一个单独类中的测试运行之前被调用的类方法。 setUpClass 会被作为唯一的参数在类上调用且必须使用 classmethod() 装饰器
tearDownClass()
在一个单独类的测试完成运行之后被调用的类方法。 tearDownClass 会被作为唯一的参数在类上调用且必须使用 classmethod() 装饰器
run(result=None)
运行测试,将结果收集至作为 result 传入的 TestResult。 如果 result 被省略或为 None,则会创建一个临时的结果对象(通过调用 defaultTestResult() 方法)并使用它。 结果对象会被返回给 run() 的调用方。
同样的效果也可通过简单地调用 TestCase 实例来达成。
skipTest(reason)
在测试方法或 setUp() 执行期间调用此方法将跳过当前测试。
subTest(msg=None, **params)
返回一个上下文管理器以将其中的代码块作为子测试来执行。 可选的 msg 和 params 是将在子测试失败时显示的任意值,以便让你能清楚地标识它们。一个测试用例可以包含任意数量的子测试声明,并且它们可以任意地嵌套。
debug()
运行测试而不收集结果。 这允许测试所引发的异常被传递给调用方,并可被用于支持在调试器中运行测试。
assertEqual(first, second, msg=None)
测试 first 和 second 是否相等。 如果两个值的比较结果是不相等,则测试将失败。
如果 first 和 second 的类型完全相同且属于 list, tuple, dict, set, frozenset 或 str 或者属于通过 addTypeEqualityFunc() 注册子类的类型则将会调用类型专属的相等判断函数以便生成更有用的默认错误消息。
assertNotEqual(first, second, msg=None)
测试 first 和 second 是否不等。 如果两个值的比较结果是相等,则测试将失败。
assertTrue(expr, msg=None)
assertFalse(expr, msg=None)
测试 expr 是否为真值(或假值)。请注意这等价于 bool(expr) is True 而不等价于 expr is True (后者要使用 assertIs(expr, True))。当存在更专门的方法时也应避免使用此方法 (例如应使用 assertEqual(a, b) 而不是 assertTrue(a == b)),因为它们在测试失败时会提供更有用的错误消息。
assertIs(first, second, msg=None)
assertIsNot(first, second, msg=None)
测试 first 和 second 是 (或不是) 同一个对象。
assertIsNone(expr, msg=None)
assertIsNotNone(expr, msg=None)
测试 expr 是 (或不是) None。
assertIn(member, container, msg=None)
assertNotIn(member, container, msg=None)
测试 member 是 (或不是) container 的成员。
assertIsInstance(obj, cls, msg=None)
assertNotIsInstance(obj, cls, msg=None)
测试 obj 是 (或不是) cls (此参数可以为一个类或包含类的元组,即 isinstance() 所接受的参数) 的实例。 要检测是否为指定类型,请使用 assertIs(type(obj), cls)。
assertRaises(exception, callable, *args, **kwds)
assertRaises(exception, *, msg=None)
测试执行callable(*args, **kwds)时是否引发了异常。如果引发了 exception 则测试通过,如果引发了另一个异常则报错,或者如果未引发任何异常则测试失败。 要捕获一组异常中的任何一个,可以将包含多个异常类的元组作为 exception 传入。
如果只给出了 exception 和可能的 msg 参数,则返回一个上下文管理器以便被测试的代码可以被写成内联形式而不是被写成函数:
with self.assertRaises(SomeException):
do_something()
# 上下文管理器将把捕获的异常对象存入在其 exception 属性中。 这适用于需要对所引发异常执行额外检查的场合:
with self.assertRaises(SomeException) as cm:
do_something()
the_exception = cm.exception
self.assertEqual(the_exception.error_code, 3)
assertRaisesRegex(exception, regex, callable, *args, **kwds)
assertRaisesRegex(exception, regex, *, msg=None)
与 assertRaises() 类似但还会测试 regex 是否匹配被引发异常的字符串表示形式。 regex 可以是一个正则表达式对象或包含正则表达式的字符串以提供给 re.search() 使用。 例如:
self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$", int, 'XYZ')
with self.assertRaisesRegex(ValueError, 'literal'):
int('XYZ')
assertWarns(warning, callable, *args, **kwds)
assertWarns(warning, *, msg=None)
测试执行callable(*args, **kwds) 是否触发了警告。如果触发了 warning 则测试通过,否则测试失败。 引发任何异常则报错。 要捕获一组警告中的任何一个,可将包含多个警告类的元组作为 warnings 传入。
如果只给出了 warning 和可能的 msg 参数,则返回一个上下文管理器以便被测试的代码可以被写成内联形式而不是被写成函数:
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)
assertWarnsRegex(warning, regex, callable, *args, **kwds)
assertWarnsRegex(warning, regex, *, msg=None)
与 assertWarns() 类似但还会测试 regex 是否匹配被触发警告的消息文本。 regex 可以是一个正则表达式对象或包含正则表达式的字符串以提供给 re.search() 使用。 例如:
self.assertWarnsRegex(DeprecationWarning, r'legacy_function\(\) is deprecated', legacy_function, 'XYZ')
with self.assertWarnsRegex(RuntimeWarning, 'unsafe frobnicating'):
frobnicate('/etc/passwd')
assertLogs(logger=None, level=None)
一个上下文管理器,它测试在 logger 或其子对象上是否至少记录了一条至少为指定 level 以上级别的消息。
如果给出了 logger 则它应为一个 logging.Logger 对象或为一个指定日志记录器名称的 str。 默认为根日志记录器,它将捕获未被非传播型后继日志记录器所拦阻的所有消息。
如果给出了 level 则它应为一个用数字表示的日志记录级别或其字符串形式 (例如 “ERROR” 或 logging.ERROR)。 默认为 logging.INFO。
如果在 with 代码块内部发出了至少一条与 logger 和 level 条件相匹配的消息则测试通过,否则测试失败。
上下文管理器返回的对象是一个记录辅助器,它会记录所匹配的日志消息。 它有两个属性:
records:所匹配的日志消息 logging.LogRecord 对象组成的列表。
output:由 str 对象组成的列表,内容为所匹配消息经格式化后的输出。
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'])
assertNoLogs(logger=None, level=None)
一个上下文管理器,它测试在 logger 或其子对象上是否未记录任何至少为指定 level 以上级别的消息。
如果给出了 logger 则它应为一个 logging.Logger 对象或为一个指定日志记录器名称的 str。 默认为根日志记录器,它将捕获所有消息。
如果给出了 level 则它应为一个用数字表示的日志记录级别或其字符串形式 (例如 “ERROR” 或 logging.ERROR)。 默认为 logging.INFO。
assertAlmostEqual(first, second, places=7, msg=None, delta=None)
assertNotAlmostEqual(first, second, places=7, msg=None, delta=None)
测试 first 与 second 是否几乎相等,比较的标准是计算差值并舍入到 places 所指定的十进制位数 (默认为 7 位),再与零相比较。 请注意此方法是将结果值舍入到指定的 十进制位数 (即相当于 round() 函数) 而非 有效位数。
如果提供了 delta 而非 places 则 first 和 second 之间的差值必须小于等于 (或大于) delta。
assertGreater(first, second, msg=None)
assertGreaterEqual(first, second, msg=None)
assertLess(first, second, msg=None)
assertLessEqual(first, second, msg=None)
根据方法名分别测试 first 是否 >, >=, < 或 <= second。 如果不是,则测试失败
assertRegex(text, regex, msg=None)
assertNotRegex(text, regex, msg=None)
测试一个 regex 搜索匹配(或不匹配) 文本。如果不匹配,错误信息中将包含匹配模式和 文本*(或部分匹配失败的 *文本)。regex 可以是正则表达式对象或能够用于 re.search() 的包含正则表达式的字符串。
assertCountEqual(first, second, msg=None)
测试序列 first 与 second 是否包含同样的元素,无论其顺序如何。 当存在差异时,将生成一条错误消息来列出两个序列之间的差异。
重复的元素 不会 在 first 和 second 的比较中被忽略。 它会检查每个元素在两个序列中的出现次数是否相同。等价于: assertEqual(Counter(list(first)), Counter(list(second))) 但还适用于包含不可哈希对象的序列。
addTypeEqualityFunc(typeobj, function)
注册一个由 assertEqual() 调用的特定类型专属方法来检查恰好为相同 typeobj (而非子类) 的两个对象是否相等。 function 必须接受两个位置参数和第三个 msg=None 关键字参数,就像 assertEqual() 那样。 当检测到前两个形参之间不相等时它必须引发 self.failureException(msg) – 可能还会提供有用的信息并在错误消息中详细解释不相等的原因。
assertEqual() 方法会将相同类型对象的相等性检查分派给不同的类型专属方法。 这些方法已被大多数内置类型所实现,但也可以使用 addTypeEqualityFunc() 来注册新的方法
assertMultiLineEqual(first, second, msg=None)
测试多行字符串 first 是否与字符串 second 相等。 当不相等时将在错误消息中包括两个字符串之间差异的高亮显示。 此方法会在通过 assertEqual() 进行字符串比较时默认被使用。
assertSequenceEqual(first, second, msg=None, seq_type=None)
测试两个序列是否相等。 如果提供了 seq_type,则 first 和 second 都必须为 seq_type 的实例否则将引发失败。 如果两个序列不相等则会构造一个错误消息来显示两者之间的差异。
此方法不会被 assertEqual() 直接调用,但它会被用于实现 assertListEqual() 和 assertTupleEqual()。
assertListEqual(first, second, msg=None)
assertTupleEqual(first, second, msg=None)
测试两个列表或元组是否相等。 如果不相等,则会构造一个错误消息来显示两者之间的差异。 如果某个形参的类型不正确也会引发错误。 这些方法会在通过 assertEqual() 进行列表或元组比较时默认被使用。
assertSetEqual(first, second, msg=None)
测试两个集合是否相等。 如果不相等,则会构造一个错误消息来列出两者之间的差异。 此方法会在通过 assertEqual() 进行集合或冻结集合比较时默认被使用。如果 first 或 second 没有 set.difference() 方法则测试失败。
assertDictEqual(first, second, msg=None)
测试两个字典是否相等。 如果不相等,则会构造一个错误消息来显示两个字典的差异。 此方法会在对 assertEqual() 的调用中默认被用来进行字典的比较。
fail(msg=None)
无条件地发出测试失败消息,附带错误消息 msg 或 None。
failureException
这个类属性给出测试方法所引发的异常。 如果某个测试框架需要使用专门的异常,并可能附带额外的信息,则必须子类化该类以便与框架“正常互动”。 这个属性的初始值为 AssertionError。
longMessage
这个类属性决定当将一个自定义失败消息作为 msg 参数传给一个失败的 assertXYY 调用时会发生什么。默认值为 True。 在此情况下,自定义消息会被添加到标准失败消息的末尾。 当设为 False 时,自定义消息会替换标准消息。
类设置可以通过在调用断言方法之前将一个实例属性 self.longMessage 赋值为 True 或 False 在单个测试方法中进行重载。
类设置会在每个测试调用之前被重置。
maxDiff
这个属性控制来自在测试失败时报告 diffs 的断言方法的 diffs 输出的最大长度。 它默认为 80*8 个字符。 这个属性所影响的断言方法有 assertSequenceEqual() (包括所有委托给它的序列比较方法), assertDictEqual() 以及 assertMultiLineEqual()。
将 maxDiff 设为 None 表示不限制 diffs 的最大长度。
countTestCases()
返回此测试对象所提供的测试数量。 对于 TestCase 实例,该数量将总是为 1。
defaultTestResult()
返回此测试类所要使用的测试结果类的实例(如果未向 run() 方法提供其他结果实例)。
对于 TestCase 实例,该返回值将总是为 TestResult 的实例;TestCase 的子类应当在有必要时重载此方法。
id()
返回一个标识指定测试用例的字符串。 该返回值通常为测试方法的完整名称,包括模块名和类名。
shortDescription()
返回测试的描述,如果未提供描述则返回 None。 此方法的默认实现将在可用的情况下返回测试方法的文档字符串的第一行,或者返回 None。
addCleanup(function, /, *args, **kwargs)
在 tearDown() 之后添加了一个要调用的函数来清理测试期间所使用的资源。 函数将按它们被添加的相反顺序被调用 (LIFO)。 它们在调用时将附带它们被添加时传给 addCleanup() 的任何参数和关键字参数。
如果 setUp() 失败,即意味着 tearDown() 未被调用,则已添加的任何清理函数仍将被调用。
enterContext(cm)
进入指定的上下文管理器。如果成功,会通过addCleanup()添加__exit__()方法为清理函数,并返回__enter__()方法的结果
doCleanups()
此方法会在 tearDown() 之后,或者如果 setUp() 引发了异常则会在 setUp() 之后被调用。
它将负责调用由 addCleanup() 添加的所有清理函数。 如果你需要在 tearDown() 之前 调用清理函数则可以自行调用 doCleanups()。
doCleanups() 每次会弹出清理函数栈中的一个方法,因此它可以在任何时候被调用。
classmethod addClassCleanup(function, /, *args, **kwargs)
在 tearDownClass() 之后添加了一个要调用的函数来清理测试类运行期间所使用的资源。 函数将按它们被添加的相反顺序被调用 (LIFO)。 它们在调用时将附带它们被添加时传给 addClassCleanup() 的任何参数和关键字参数。
如果 setUpClass() 失败,即意味着 tearDownClass() 未被调用,则已添加的任何清理函数仍将被调用。
classmethod enterClassContext(cm)
进入指定的上下文管理器。如果成功,会通过addClassCleanup()添加__exit__()方法为清理函数,并返回__enter__()方法的结果
classmethod doClassCleanups()
此方法会在 tearDownClass() 之后无条件地被调用,或者如果 setUpClass() 引发了异常则会在 setUpClass() 之后被调用。
它将负责访问由 addClassCleanup() 添加的所有清理函数。 如果你需要在 tearDownClass() 之前 调用清理函数则可以自行调用 doClassCleanups()。
doClassCleanups() 每次会弹出清理函数栈中的一个方法,因此它在任何时候被调用。
这个类提供了与 TestCase 类似的 API 并也接受协程作为测试函数。
coroutine asyncSetUp()
为测试预备而调用的方法。 此方法会在 setUp() 之后被调用。 此方法将在调用测试方法之前立即被调用;除了 AssertionError 或 SkipTest,此方法所引发的任何异常都将被视为错误而非测试失败。 默认的实现将不做任何事情。
coroutine asyncTearDown()
在测试方法被调用并记录结果之后立即被调用的方法。 此方法会在 tearDown() 之前被调用。 此方法即使在测试方法引发异常时仍会被调用,因此子类中的实现将需要特别注意检查内部状态。 除 AssertionError 或 SkipTest 外,此方法所引发的任何异常都将被视为额外的错误而非测试失败(因而会增加总计错误报告数)。 此方法将只在 asyncSetUp() 成功执行时被调用,无论测试方法的结果如何。 默认的实现将不做任何事情。
addAsyncCleanup(function, /, *args, **kwargs)
此方法接受一个可被用作清理函数的协程。
coroutine enterAsyncContext(cm)
进入指定的上下文管理器。如果成功,会通过addAsyncCleanup()添加__aexit__()方法为清理函数,并返回__aenter__()方法的结果
run(result=None)
设置一个新的事件循环来运行测试,将结果收集至作为 result 传入的 TestResult。 如果 result 被省略或为 None,则会创建一个临时的结果对象(通过调用 defaultTestResult() 方法)并使用它。 结果对象会被返回给 run() 的调用方。 在测试结束时事件循环中的所有任务都将被取消。
这个类实现了 TestCase 的部分接口,允许测试运行方驱动测试,但不提供可被测试代码用来检查和报告错误的方法。 这个类被用于创建使用传统测试代码的测试用例,允许它被集成到基于 unittest 的测试框架中。
这个类代表对单独测试用例和测试套件的聚合。 这个类提供给测试运行方所需的接口以允许其像任何其他测试用例一样运行。 运行一个 TestSuite 实例与对套件执行迭代来逐一运行每个测试的效果相同。
如果给出了 tests,则它必须是一个包含单独测试用例的可迭代对象或是将被用于初始构建测试套件的其他测试套件。 还有一些附加的方法会被提供用来在随后向测试集添加测试用例和测试套件。
TestSuite 对象的行为与 TestCase 对象很相似,区别在于它们并不会真正实现一个测试。 它们会被用来将测试聚合为多个要同时运行的测试分组。
addTest(test)
向测试套件添加 TestCase 或 TestSuite。
addTests(tests)
将来自包含 TestCase 和 TestSuite 实例的可迭代对象的所有测试添加到这个测试套件。这等价于对 tests 进行迭代,并为其中的每个元素调用 addTest()。
run(result)
运行与这个套件相关联的测试,将结果收集到作为 result 传入的测试结果对象中。 请注意与 TestCase.run() 的区别,TestSuite.run() 必须传入结果对象。
在 TestSuite 对象的典型应用中,run() 方法是由 TestRunner 发起调用而不是由最终用户测试来控制。
debug()
运行与这个套件相关联的测试而不收集结果。 这允许测试所引发的异常被传递给调用方并可被用于支持在调试器中运行测试。
countTestCases()
返回此测试对象所提供的测试数量,包括单独的测试和子套件。
__iter__()
由 TestSuite 分组的测试总是可以通过迭代来访问。 其子类可以通过重载 iter() 来惰性地提供测试。 请注意此方法可在单个套件上多次被调用(例如在计数测试或相等性比较时),为此在 TestSuite.run() 之前重复迭代所返回的测试对于每次调用迭代都必须相同。在 TestSuite.run() 之后,调用方不应继续访问此方法所返回的测试,除非调用方使用重载了TestSuite._removeTestAtIndex() 的子类来保留对测试的引用。
TestLoader 类可被用来基于类和模块创建测试套件。 通常,没有必要创建该类的实例;unittest 模块提供了一个可作为 unittest.defaultTestLoader 共享的实例。 但是,使用子类或实例允许对某些配置属性进行定制。
这个类被用于编译有关哪些测试执行成功而哪些失败的信息。
存放一组测试的结果的 TestResult 对象。 TestCase 和 TestSuite 类将确保结果被正确地记录;测试创建者无须担心如何记录测试的结果。
建立在 unittest 之上的测试框架可能会想要访问通过运行一组测试所产生的 TestResult 对象用来报告信息;TestRunner.run() 方法是出于这个目的而返回 TestResult 实例的。
TextTestRunner使用的TestResult的具体实现。子类应接受**kwargs,以确保接口更改时的兼容性。
用于分享的 TestLoader 类实例。 如果不需要自制 TestLoader,则可以使用该实例而不必重复创建新的实例。
一个将结果输出到流的基本测试运行器。 如果 stream 为默认的 None,则会使用 sys.stderr 作为输出流。 这个类具有一些配置形参,但实际上都非常简单。 运行测试套件的图形化应用程序应当提供替代实现。 这样的实现应当在添加新特性到 unittest 时接受 **kwargs 作为修改构造运行器的接口。
默认情况下,此运行程序显示DeprecationWarning、PendingDeprecationWarning、ResourceWarning和ImportWarning,即使它们在默认情况下被忽略。可以使用Python的-Wd或-Wa选项覆盖此行为,并将warnings保留为None。
从 module 加载一组测试并运行它们的命令行程序;这主要是为了让测试模块能方便地执行。
模块或包可以通过实现一个名为 load_tests 的函数来定制在正常测试运行或测试发现期间要如何从中加载测试。
如果一个测试模块定义了 load_tests 则它将被 TestLoader.loadTestsFromModule() 调用并传入下列参数:
load_tests(loader, standard_tests, pattern)
它应当返回一个 TestSuite。
loader 是执行载入操作的 TestLoader 实例。
standard_tests 是默认要从该模块载入的测试。 测试模块通常只需从标准测试集中添加或移除测试。
pattern 会通过 loadTestsFromModule 传入。 它的默认值为 None。在作为测试发现的一部分载入包时使用的。
一个从指定 TestCase 类集合中载入测试的 load_tests 函数看起来可能是这样的:
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
如果发现操作是在一个包含包的目录中开始的,不论是通过命令行还是通过调用 TestLoader.discover(),则将在包__init__.py 中检查 load_tests。 如果不存在此函数,则发现将在包内部执行递归,就像它是另一个目录一样。 在其他情况下,包中测试的发现操作将留给 load_tests 执行,它将附带下列参数被调用:
load_tests(loader, standard_tests, pattern)
这应当返回代表包中所有测试的 TestSuite。 (standard_tests 将只包含从 __init__.py 获取的测试。)
因为模式已被传入 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
类与模块设定是在 TestSuite 中实现的。 当测试套件遇到来自新类的测试时则来自之前的类(如果存在)的 tearDownClass() 会被调用,然后再调用来自新类的 setUpClass()。
类似地如果测试是来自之前的测试的另一个模块则来自之前模块的 tearDownModule 将被运行,然后再运行来自新模块的 setUpModule。
在所有测试运行完毕后最终的 tearDownClass 和 tearDownModule 将被运行。
请注意共享设定不适用于一些 [潜在的] 特性例如测试并行化并且它们会破坏测试隔离。 它们应当被谨慎地使用。
由 unittest 测试加载器创建的测试的默认顺序是将所有来自相同模块和类的测试归入相同分组。 这将导致 setUpClass / setUpModule (等) 对于每个类和模块都恰好被调用一次。 如果你将顺序随机化,以便使得来自不同模块和类的测试彼此相邻,那么这些共享的设定函数就可能会在一次测试运行中被多次调用。
共享的设定不适用与非标准顺序的套件。 对于不想支持共享设定的框架来说 BaseTestSuite 仍然可用。
如果在共享的设定函数中引发了任何异常则测试将被报告错误。 因为没有对应的测试实例,所以会创建一个 _ErrorHolder 对象(它具有与 TestCase 相同的接口)来代表该错误。 如果你只是使用标准 unittest 测试运行器那么这个细节并不重要,但是如果你是一个框架开发者那么这可能会有关系。
应当被实现为函数
def setUpModule():
createConnection()
def tearDownModule():
closeConnection()
如果在 setUpModule 中引发了异常则模块中的任何测试都将不会被运行并且 tearDownModule 也不会被运行。 如果引发的异常是 SkipTest 异常则模块将被报告为已跳过而非发生错误。
要添加即使在发生异常时也必须运行的清理代码,请使用 addModuleCleanup
unittest.addModuleCleanup(function, /, *args, **kwargs)
在 tearDownModule() 之后添加一个要调用的函数来清理测试类运行期间所使用的资源。 函数将按它们被添加的相反顺序被调用 (LIFO)。 它们在调用时将附带它们被添加时传给 addModuleCleanup() 的任何参数和关键字参数。
如果 setUpModule() 失败,即意味着 tearDownModule() 未被调用,则已添加的任何清理函数仍将被调用。
classmethod unittest.enterModuleContext(cm)
进入指定的上下文管理器。如果成功,会通过addModuleCleanup()添加__exit__()方法为清理函数,并返回__enter__()方法的结果
unittest.doModuleCleanups()
此函数会在 tearDownModule() 之后无条件地被调用,或者如果 setUpModule() 引发了异常则会在 setUpModule() 之后被调用。
它将负责调用由addModuleCleanup() 添加的所有清理函数。 如果你需要在 tearDownModule() 之前 调用清理函数则可以自行调用 doModuleCleanups()。
doModuleCleanups() 每次会弹出清理函数栈中的一个方法,因此它可以在任何时候被调用。
unittest 的 -c/–catch 命令行选项,加上 unittest.main() 的 catchbreak 形参,提供了在测试运行期间处理 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 的处理。 注册一个结果将保存指向它的弱引用,因此这并不能防止结果被作为垃圾回收。
如果 control-C 未被启用则注册 TestResult 对象将没有任何附带影响,因此不论是否启用了该项处理测试框架都可以无条件地注册他们独立创建的所有结果。
unittest.removeResult(result)
移除一个已注册的结果。 一旦结果被移除则 stop() 将不再会作为针对 control-C 的响应在结果对象上被调用。
unittest.removeHandler(function=None)
当不附带任何参数被调用时此函数将移除已被安装的 control-C 处理句柄。 此函数还可被用作测试装饰器以在测试被执行时临时性地移除处理句柄:
@unittest.removeHandler
def test_signal_handling(self):
unittest.mock 是一个用于测试的Python库。它允许使用模拟对象来替换受测系统的一些部分,并对这些部分如何被使用进行断言判断。
unittest.mock 提供的 Mock 类,能在整个测试套件中模拟大量的方法。创建后,就可以断言调用了哪些方法/属性及其参数。还可以以常规方式指定返回值并设置所需的属性。
此外,mock 还提供了用于修补测试范围内模块和类级别属性的 patch() 装饰器,和用于创建独特对象的 sentinel 。
Mock 是一个可以灵活的替换存根 (stubs) 的对象,可以测试所有代码。 Mock 是可调用的,在访问其属性时创建一个新的 mock [1] 。访问相同的属性只会返回相同的 mock 。 Mock 会保存调用记录,可以通过断言获悉代码的调用。
MagicMock 是 Mock 的子类,它有所有预创建且可使用的魔术方法。在需要模拟不可调用对象时,可以使用 NonCallableMock 和 NonCallableMagicMock
patch() 装饰器使得用 Mock 对象临时替换特定模块中的类非常方便。 默认情况下 patch() 将为你创建一个 MagicMock。 你可以使用 patch() 的 new_callable 参数指定替代 Mock 的类。
创建一个新的 Mock 对象。通过可选参数指定 Mock 对象的行为:
spec: 可以是字符串列表,也可以是充当模拟对象规范的现有对象(类或实例)。如果传入一个对象,则通过在该对象上调用 dir 来生成字符串列表(不支持的魔法属性和方法除外)。访问不在此列表中的任何属性都将触发 AttributeError 。
spec_set :spec 的更严格的变体。如果使用了该属性,尝试模拟 set 或 get 的属性不在 spec_set 所包含的对象中时,会抛出 AttributeError 。
side_effect :每当调用 Mock 时都会调用的函数。 参见 side_effect 属性。 对于引发异常或动态更改返回值很有用。 该函数使用与 mock 函数相同的参数调用,并且除非返回 DEFAULT ,否则该函数的返回值将用作返回值。
另外, side_effect 可以是异常类或实例。 此时,调用模拟程序时将引发异常。
如果 side_effect 是可迭代对象,则每次调用 mock 都将返回可迭代对象的下一个值。
设置 side_effect 为 None 即可清空。
return_value :调用 mock 的返回值。 默认情况下,是一个新的Mock(在首次访问时创建)。 参见 return_value 属性 。
unsafe: 默认情况下,访问任何名称以assert、assret、asert、aseert或assrt开头的属性都会引发AttributeError。传递unsafe=True将允许访问这些属性。
wraps :要包装的 mock 对象。 如果 wraps 不是 None ,那么调用 Mock 会将调用传递给 wraps 的对象(返回实际结果)。 对模拟的属性访问将返回一个 Mock 对象,该对象包装了 wraps 对象的相应属性(因此,尝试访问不存在的属性将引发 AttributeError )。
如果明确指定 return_value ,则调用时不会返回包装对象,而是返回 return_value 。
name :mock 的名称。 在调试时很有用。 名称会传递到子 mock 。
assert_called()
断言 mock 已被调用至少一次。
assert_called_once()
断言 mock 已被调用恰好一次。
assert_called_with(*args, **kwargs)
此方法是断言上次调用已以特定方式进行的一种便捷方法:
mock = Mock()
mock.method(1, 2, 3, test='wow')
mock.method.assert_called_with(1, 2, 3, test='wow')
assert_called_once_with(*args, **kwargs)
断言 mock 已被调用恰好一次,并且向该调用传入了指定的参数。
assert_any_call(*args, **kwargs)
断言 mock 已被调用并附带了指定的参数。
如果 mock 曾经 被调用过则断言通过,不同于 assert_called_with() 和assert_called_once_with() 那样只有在调用是最近的一次时才会通过,而对于assert_called_once_with() 则它还必须是唯一的一次调用。
assert_has_calls(calls, any_order=False)
断言 mock 已附带指定的参数被调用。 将针对这些调用检查 mock_calls 列表。
如果 any_order 为假值则调用必须是顺序进行的。 在指定的调用之前或之后还可以有额外的调用。
如果 any_order 为真值则调用可以是任意顺序的,但它们都必须在 mock_calls 中出现。
mock = Mock(return_value=None)
mock(1)
mock(2)
mock(3)
mock(4)
calls = [call(2), call(3)]
mock.assert_has_calls(calls)
calls = [call(4), call(2), call(3)]
mock.assert_has_calls(calls, any_order=True)
assert_not_called()
断言 mock 从未被调用过。
reset_mock(*, return_value=False, side_effect=False)
将在 mock 对象上重置所有的调用属性。
请注意 reset_mock() 不会 清除返回值、side_effect 或任何你使用普通赋值所默认设置的子属性。
如果你想要重置 return_value 或 side_effect,则要为相应的形参传入 True。 子 mock 和返回值 mock (如果有的话) 也会被重置。
mock_add_spec(spec, spec_set=False)
为 mock 添加描述。 spec 可以是一个对象或字符串列表。 只有 spec 上的属性可以作为来自 mock 的属性被获取。如果 spec_set 为真值则只有 spec 上的属性可以被设置。
attach_mock(mock, attribute)
附加一个 mock 作为这一个的属性,替换它的名称和上级。 对附加 mock 的调用将记录在这一个的 method_calls 和 mock_calls 属性中。
configure_mock(**kwargs)
通过关键字参数在 mock 上设置属性。configure_mock() 的存在是使得 mock 被创建之后的配置更为容易。
属性加返回值和附带影响可以使用标准点号标记在子 mock 上设置并在方法调用中解包一个字典:
mock = Mock()
attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
mock.configure_mock(**attrs)
mock.method()
3
mock.other()
Traceback (most recent call last):
...
KeyError
同样的操作可在对 mock 的构造器调用中达成:
attrs = {‘method.return_value’: 3, ‘other.side_effect’: KeyError}
mock = Mock(some_attribute=‘eggs’, **attrs)
mock.some_attribute
‘eggs’
mock.method()
3
mock.other()
Traceback (most recent call last):
…
KeyError
__dir__()
Mock 对象会将 dir(some_mock) 的结果限制为有用结果。 对于带有 spec 的 mock 这还包括 mock 的所有被允许的属性。
_get_child_mock(**kw)
创建针对属性和返回值的子 mock。 默认情况下子 mock 将为与其上级相同的类型。 Mock 的子类可能需要重载它来定制子 mock 的创建方式。
对于非可调用的 mock 将会使用可调用的变化形式(而非不是任意的自定义子类)。
called
一个表示 mock 对象是否已被调用的布尔值
call_count
一个告诉你 mock 对象已被调用多少次的整数值
return_value
设置这个来配置通过调用该 mock 所返回的值
side_effect
可以是mock调用时要执行的函数,可以是可迭代对象,也可以是要引发的异常(类或实例)
如果你传入一个函数则它将附带与该 mock 相同的参数被调用并且除了该函数返回 DEFAULT 单例的情况以外对该 mock 的调用都将随后返回该函数所返回的任何东西。 如果该函数返回 DEFAULT 则该 mock 将返回其正常值 (来自 return_value)。
如果你传入一个可迭代对象,它会被用来获取一个在每次调用时必须产生一个值的迭代器。 这个值可以是一个要被引发的异常实例,或是一个要从该调用返回给 mock 的值 (DEFAULT 处理与函数的情况一致)。
一个引发异常(来测试 API 的异常处理)的 mock 的示例:
mock = Mock()
mock.side_effect = Exception('Boom!')
mock()
Traceback (most recent call last):
...
Exception: Boom!
使用 side_effect 来返回包含多个值的序列:
mock = Mock()
mock.side_effect = [3, 2, 1]
mock(), mock(), mock()
(3, 2, 1)
使用一个可调用对象:
mock = Mock(return_value=3)
def side_effect(*args, **kwargs):
return DEFAULT
mock.side_effect = side_effect
mock()
3
side_effect 可以在构造器中设置。 下面是在 mock 被调用时增加一个该属性值并返回它的例子:
side_effect = lambda value: value + 1
mock = Mock(side_effect=side_effect)
mock(3)
4
mock(-8)
-7
将 side_effect 设为 None 可以清除它:
call_args
这可以是 None (如果 mock 没有被调用),或是 mock 最近一次被调用时附带的参数。 这将采用元组的形式:第一个成员也可以通过 args 特征属性来访问,它是 mock 被调用时所附带的任何位置参数 (或为空元组),而第二个成员也可以通过 kwargs 特征属性来访问,它则是任何关键字参数 (或为空字典)。
call_args,以及列表 call_args_list, method_calls 和 mock_calls 的成员都是 call 对象。 这些对象属性元组,因此它们可被解包以获得单独的参数并创建更复杂的断言。
mock = Mock(return_value=None)
print(mock.call_args)
None
mock()
mock.call_args
call()
mock.call_args == ()
True
mock(3, 4)
mock.call_args
call(3, 4)
mock.call_args == ((3, 4),)
True
mock.call_args.args
(3, 4)
mock.call_args.kwargs
{}
mock(3, 4, 5, key='fish', next='w00t!')
mock.call_args
call(3, 4, 5, key='fish', next='w00t!')
mock.call_args.args
(3, 4, 5)
mock.call_args.kwargs
{'key': 'fish', 'next': 'w00t!'}
call_args_list
这是一个已排序的对 mock 对象的所有调用的列表(因此该列表的长度就是它已被调用的次数)。 在执行任何调用之前它将是一个空列表。 call 对象可以被用来方便地构造调用列表以与 call_args_list 相比较。
mock = Mock(return_value=None)
mock()
mock(3, 4)
mock(key='fish', next='w00t!')
mock.call_args_list
[call(), call(3, 4), call(key='fish', next='w00t!')]
expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)]
mock.call_args_list == expected
True
method_calls
除了会追踪对其自身的调用,mock 还会追踪对方法和属性,以及 它们的 方法和属性的访问:
mock = Mock()
mock.method()
mock.property.method.attribute()
mock.method_calls
[call.method(), call.property.method.attribute()]
mock_calls
mock_calls 会记录所有对 mock 对象、它的方法、魔术方法的调用 以及 返回值的 mock。
mock = MagicMock()
result = mock(1, 2, 3)
mock.first(a=3)
mock.second()
int(mock)
1
result(1)
expected = [call(1, 2, 3), call.first(a=3), call.second(),
call.__int__(), call()(1)]
mock.mock_calls == expected
True
mock_calls 的记录方式意味着在进行嵌套调用时,之前调用的形参不会被记录因而这样的比较将总是相等:
mock = MagicMock()
mock.top(a=3).bottom()
mock.mock_calls
[call.top(a=3), call.top().bottom()]
mock.mock_calls[-1] == call.top(a=-1).bottom()
True
__class__
通常一个对象的 class 属性将返回其类型。 对于具有 spec 的 mock 对象来说,class 将改为返回 spec 类。 这将允许 mock 对象为它们所替换 / 屏蔽的对象跳过 isinstance() 测试:
mock = Mock(spec=3)
isinstance(mock, int)
True
__class__ 是可以被赋值的,这将允许 mock 跳过 isinstance() 检测而不强制要求你使用 spec:
mock = Mock()
mock.__class__ = dict
isinstance(mock, dict)
True
不可调用的 Mock 版本。 其构造器的形参具有与 Mock 相同的含义,区别在于 return_value 和 side_effect 在不可调用的 mock 上没有意义。
旨在作为类的特征属性或其他描述器使用的 mock。 PropertyMock 提供了__get__() 和__set__() 方法以便你可以在它被提取时指定一个返回值。
当从一个对象提取 PropertyMock 实例时将不附带任何参数地调用该 mock。 如果设置它则调用该 mock 时将附带被设置的值。
class Foo:
@property
def foo(self):
return 'something'
@foo.setter
def foo(self, value):
pass
with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo:
mock_foo.return_value = 'mockity-mock'
this_foo = Foo()
print(this_foo.foo)
this_foo.foo = 6
mockity-mock
mock_foo.mock_calls
[call(), call(6)]
由于 mock 属性的存储方式你无法直接将 PropertyMock 附加到一个 mock 对象。 但是你可以将它附加到 mock 类型对象:
m = MagicMock()
p = PropertyMock(return_value=3)
type(m).foo = p
m.foo
3
p.assert_called_once_with()
MagicMock 的异步版本。 AsyncMock 对象的行为方式将使该对象被识别为异步函数,其调用的结果将为可等待对象。
用于多线程测试的MagicMock版本。ThreadingMock对象提供了额外的方法来等待调用,而不是立即断言调用。
默认超时由timeout参数指定,或者如果未设置,则由ThreadingMock.default_timeout属性指定,该属性默认为阻塞(None)。
您可以通过设置ThreadingMock.default_timeout来配置全局默认超时。
Mock 对象是可调用对象。 调用将把值集合作为 return_value 属性返回。 默认的返回值是一个新的 Mock 对象;它会在对返回值的首次访问(不论是显式访问还是通过调用 Mock)时被创建 —— 但它会被保存并且每次都返回相同的对象。
对该对象的调用将被记录在 call_args 和 call_args_list 等属性中。
如果设置了 side_effect 则它将在调用被记录之后被调用,因此如果 side_effect 引发了异常该调用仍然会被记录。
Mock 对象会根据需要创建属性。 这允许它们可以假装成任意类型的对象。
你可能想要一个 mock 对象在调用 hasattr() 时返回 False,或者在获取某个属性时引发 AttributeError。 你可以通过提供一个对象作为 mock 的 spec 属性来做到这点,但这并不总是很方便。你可以通过删除属性来“屏蔽”它们。 属性一旦被删除,访问它将引发 AttributeError。
mock = MagicMock()
hasattr(mock, 'm')
True
del mock.m
hasattr(mock, 'm')
False
del mock.f
mock.f
Traceback (most recent call last):
...
AttributeError: f
由于 “name” 是 Mock 构造器的参数之一,如果你想让你的 mock 对象具有 “name” 属性你不可以在创建时传入该参数。 有两个替代方式。 一个选项是使用 configure_mock():
mock = MagicMock()
mock.configure_mock(name='my_name')
mock.name
'my_name'
一个更简单的选项是在 mock 创建之后简单地设置 “name” 属性:
mock = MagicMock()
mock.name = "foo"
当你附加一个 mock 作为另一个 mock 的属性(或作为返回值)时它会成为该 mock 的 “子对象”。 对子对象的调用会被记录在父对象的 method_calls 和 mock_calls 属性中。 这适用于配置子 mock 然后将它们附加到父对象,或是将 mock 附加到将记录所有对子对象的调用的父对象上并允许你创建有关 mock 之间的调用顺序的断言:
parent = MagicMock()
child1 = MagicMock(return_value=None)
child2 = MagicMock(return_value=None)
parent.child1 = child1
parent.child2 = child2
child1(1)
child2(2)
parent.mock_calls
[call.child1(1), call.child2(2)]
这里有一个例外情况是如果 mock 设置了名称。 这允许你在出于某些理由不希望其发生时避免 “父对象” 的影响。
mock = MagicMock()
not_a_child = MagicMock(name='not-a-child')
mock.attribute = not_a_child
mock.attribute()
mock.mock_calls
[]
通过 patch() 创建的 mock 会被自动赋予名称。 要将具有名称的 mock 附加到父对象上你应当使用 attach_mock() 方法:
thing1 = object()
thing2 = object()
parent = MagicMock()
with patch('__main__.thing1', return_value=None) as child1:
with patch('__main__.thing2', return_value=None) as child2:
parent.attach_mock(child1, 'child1')
parent.attach_mock(child2, 'child2')
child1('one')
child2('two')
parent.mock_calls
[call.child1('one'), call.child2('two')]
patch 装饰器仅被用于在它们所装饰的函数作用域内部为对象添加补丁。 它们会自动为你执行去除补丁的处理,即使是在引发了异常的情况下。 所有这些函数都还可在 with 语句中使用或是作为类装饰器。
patch() 可以作为函数装饰器、类装饰器或上下文管理器。 在函数或 with 语句的内部,target 会打上一个 new 对象补丁。 当函数/with 语句退出时补丁将被撤销。
如果 new 被省略,那么如果被打补丁的对象是一个异步函数则 target 将被替换为 AsyncMock 否则替换为 MagicMock。 如果 patch() 被用作装饰器并且 new 被省略,那么已创建的 mock 将作为一个附加参数传入被装饰的函数。 如果 patch() 被用作上下文管理器那么已创建的 mock 将被该上下文管理器所返回。
target 应当为 ‘package.module.ClassName’ 形式的字符串。 target 将被导入并且该指定对象会被替换为 new 对象,因此 target 必须是可以从你调用 patch() 的环境中导入的。 target 会在被装饰的函数被执行的时候被导入,而非在装饰的时候。
spec 和 spec_set 关键字参数会被传递给 MagicMock,如果 patch 为你创建了此对象的话。此外你还可以传入 spec=True 或 spec_set=True,这将使 patch 将被模拟的对象作为 spec/spec_set 对象传入。
new_callable 允许你指定一个不同的类,或者可调用对象,它将被调用以创建 新的 对象。 在默认情况下将指定 AsyncMock 用于异步函数,MagicMock 用于其他函数。
另一种更强形式的 spec 是 autospec。 如果你设置了 autospec=True 则将以来自被替换对象的 spec 来创建 mock。 mock 的所有属性也将具有被替换对象相应属性的 spec。 被模拟的方法和函数将检查它们的参数并且如果使用了错误的签名调用它们则将引发 TypeError。 对于替换了一个类的 mock,它们的返回值(即‘实例’)将具有与该类相同的 spec。
除了 autospec=True 你还可以传入 autospec=some_object 以使用任意对象而不是被替换的对象作为 spec。
在默认情况下 patch() 将无法替换不存在的属性。 如果你传入 create=True,且该属性并不存在,则 patch 将在调用被打补丁的函数时为你创建该属性,并在退出被打补丁的函数时再次删除它。 这适用于编写针对生产代码在运行时创建的属性的测试。 它默认是被关闭的因为这具有危险性。 当它被开启时你将能够针对实际上并不存在的 API 编写通过测试!
Patch 可以被用作 TestCase 类装饰器。 其作用是装饰类中的每个测试方法。 当你的测试方法共享同一个补丁集时这将减少模板代码。patch() 会通过查找以 patch.TEST_PREFIX 打头的名称来找到测试。 其默认值为 ‘test’,这与 unittest 打到测试的方式一致。 你可以通过设置 patch.TEST_PREFIX 来指定其他的前缀。
Patch 可以通过 with 语句作为上下文管理器使用。 这时补丁将应用于 with 语句的缩进代码块。 如果你使用了 “as” 则打补丁的对象将被绑定到 “as” 之后的名称;这非常适用于当 patch() 为你创建 mock 对象的情况。
patch() 可接受任意关键字参数。 如果打补丁的对象是异步的则这些参数将被传给 AsyncMock,否则传给 MagicMock,或者是指定的 new_callable。
patch() 作为函数装饰器,为你创建 mock 并将其传入被装饰的函数:
@patch('__main__.SomeClass')
def function(normal_argument, mock_class):
print(mock_class is SomeClass)
function(None)
True
为类打补丁将把该类替换为 MagicMock 的 实例。 如果该类是在受测试的代码中被实例化的则它将为所要使用的 mock 的 return_value。如果该类被多次实例化则你可以使用 side_effect 来每次返回一个新 mock。 或者你也可以将 return_value 设为你希望的任何对象。
要在被打补丁的类的 实例 的方法上配置返回值你必须在 return_value 操作。 例如:
class Class:
def method(self):
pass
with patch('__main__.Class') as MockClass:
instance = MockClass.return_value
instance.method.return_value = 'foo'
assert Class() is instance
assert Class().method() == 'foo'
如果你使用 spec 或 spec_set 并且 patch() 替换的是 class,那么所创建的 mock 的返回值将具有同样的 spec。
Original = Class
patcher = patch('__main__.Class', spec=True)
MockClass = patcher.start()
instance = MockClass()
assert isinstance(instance, Original)
patcher.stop()
new_callable 参数适用于当你想要使用其他类来替代所创建的 mock 默认的 MagicMock 的场合。 例如,如果你想要使用 NonCallableMock:
thing = object()
with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
assert thing is mock_thing
thing()
Traceback (most recent call last):
...
TypeError: 'NonCallableMock' object is not callable
另一个使用场景是用 io.StringIO 实例来替换某个对象:
from io import StringIO
def foo():
print('Something')
@patch('sys.stdout', new_callable=StringIO)
def test(mock_stdout):
foo()
assert mock_stdout.getvalue() == 'Something\n'
test()
当 patch() 为你创建 mock 时,通常你需要做的第一件事就是配置该 mock。 某些配置可以在对 patch 的调用中完成。 你在调用时传入的任何关键字参数都将被用来在所创建的 mock 上设置属性:
patcher = patch('__main__.thing', first='one', second='two')
mock_thing = patcher.start()
mock_thing.first
'one'
mock_thing.second
'two'
除了所创建的 mock 的属性上的属性,例如 return_value 和 side_effect,还可以配置子 mock 的属性。 将这些属性直接作为关键字参数传入在语义上是无效的,但是仍然能够使用 ** 将以这些属性为键的字典扩展至一个 patch() 调用中
config = {'method.return_value': 3, 'other.side_effect': KeyError}
patcher = patch('__main__.thing', **config)
mock_thing = patcher.start()
mock_thing.method()
3
mock_thing.other()
Traceback (most recent call last):
...
KeyError
在默认情况下,尝试给某个模块中并不存在的函数(或者某个类中的方法或属性)打补丁将会失败并引发 AttributeError,但在对 patch() 的调用中添加 create=True 将符合预期
@patch('sys.non_existing_attribute', 42, create=True)
def test(mock_stdout):
assert sys.non_existing_attribute == 42
test()
用一个 mock 对象为target对象中指定名称的attribute成员打补丁。
patch.object() 可以被用作装饰器、类装饰器或上下文管理器。 new, spec, create, spec_set, autospec 和 new_callable 等参数的含义与 patch() 的相同。 与 patch() 类似,patch.object() 接受任意关键字参数用于配置它所创建的 mock 对象。
当用作类装饰器时 patch.object() 将认可 patch.TEST_PREFIX 作为选择所要包装方法的标准。
你可以附带三个参数或两个参数来调用 patch.object()。 三个参数的形式将接受要打补丁的对象、属性的名称以及将要替换该属性的对象。
将附带两个参数的形式调用时你将省略替换对象,还会为你创建一个 mock 并作为附加参数传入被装饰的函数:
@patch.object(SomeClass, 'class_method')
def test(mock_method):
SomeClass.class_method(3)
mock_method.assert_called_with(3)
test()
为一个字典或字典类对象打补丁,并在测试之后将该目录恢复到其初始状态。
in_dict 可以是一个字典或映射类容器。 如果它是一个映射则它必须至少支持获取、设置和删除条目以及对键执行迭代。
in_dict 也可以是一个指定字典名称的字符串,然后将通过导入操作来获取该字典。
values 可以是一个要在字典中设置的值的字典。 values 也可以是一个包含 (key, value) 对的可迭代对象。
如果 clear 为真值则该字典将在设置新值之前先被清空。
patch.dict() 也可以附带任意关键字参数调用以设置字典中的值。
patch.dict() 可被用作上下文管理器、装饰器或类装饰器:
foo = {}
@patch.dict(foo, {'newkey': 'newvalue'})
def test():
assert foo == {'newkey': 'newvalue'}
test()
assert foo == {}
当被用作类装饰器时 patch.dict() 将认可 patch.TEST_PREFIX (默认值为 ‘test’) 作为选择所在包装方法的标准,如果你在为你的测试使用不同的前缀,你可以通过设置 patch.TEST_PREFIX 来将不同的前缀告知打补丁方。
import os
import unittest
from unittest.mock import patch
@patch.dict('os.environ', {'newkey': 'newvalue'})
class TestSample(unittest.TestCase):
def test_sample(self):
self.assertEqual(os.environ['newkey'], 'newvalue')
patch.dict() 可被用来向一个字典添加成员,或者简单地让测试修改一个字典,并确保当测试结束时恢复该字典。
foo = {}
with patch.dict(foo, {'newkey': 'newvalue'}) as patched_foo:
assert foo == {'newkey': 'newvalue'}
assert patched_foo == {'newkey': 'newvalue'}
# You can add, update or delete keys of foo (or patched_foo, it's the same dict)
patched_foo['spam'] = 'eggs'
assert foo == {}
assert patched_foo == {}
import os
with patch.dict('os.environ', {'newkey': 'newvalue'}):
print(os.environ['newkey'])
newvalue
assert 'newkey' not in os.environ
可以在 patch.dict() 调用中使用关键字来设置字典的值:
mymodule = MagicMock()
mymodule.function.return_value = 'fish'
with patch.dict('sys.modules', mymodule=mymodule):
import mymodule
mymodule.function('some', 'args')
'fish'
patch.dict() 可以用于实际上不是字典的字典类对象。 它们最少必须支持条目获取、设置、删除以及迭代或成员检测两者中的一个。 这对应于魔术方法__getitem__(),__setitem__(),__delitem__() 以及__iter__() 或__contains__()。
class Container:
def __init__(self):
self.values = {}
def __getitem__(self, name):
return self.values[name]
def __setitem__(self, name, value):
self.values[name] = value
def __delitem__(self, name):
del self.values[name]
def __iter__(self):
return iter(self.values)
thing = Container()
thing['one'] = 1
with patch.dict(thing, one=2, two=3):
assert thing['one'] == 2
assert thing['two'] == 3
assert thing['one'] == 1
assert list(thing) == ['one']
在单个调用中执行多重补丁。 它接受要打补丁的对象(一个对象或一个通过导入来获取对象的字符串)以及用于补丁的关键字参数:
with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'):
...
如果你希望 patch.multiple() 为你创建 mock 则要使用 DEFAULT 作为值。 在此情况下所创建的 mock 会通过关键字参数传入被装饰的函数,而当 patch.multiple() 被用作上下文管理器时则将返回一个字典。
patch.multiple() 可以被用作装饰器、类装饰器或上下文管理器。 spec, spec_set, create, autospec 和 new_callable 等参数的含义与 patch() 的相同。 这些参数将被应用到 patch.multiple() 所打的 所有 补丁。
当被用作类装饰器时 patch.multiple() 将认可 patch.TEST_PREFIX 作为选择所要包装方法的标准。
如果你希望 patch.multiple() 为你创建 mock,那么你可以使用 DEFAULT 作为值。 如果你使用 patch.multiple() 作为装饰器则所创建的 mock 会作为关键字参数传入被装饰的函数。
thing = object()
other = object()
@patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
def test_function(thing, other):
assert isinstance(thing, MagicMock)
assert isinstance(other, MagicMock)
test_function()
patch.multiple() 可以与其他 patch 装饰器嵌套使用,但要将作为关键字传入的参数要放在 patch() 所创建的标准参数 之后:
@patch('sys.exit')
@patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
def test_function(mock_exit, other, thing):
assert 'other' in repr(other)
assert 'thing' in repr(thing)
assert 'exit' in repr(mock_exit)
test_function()
如果 patch.multiple() 被用作上下文管理器,则上下文管理器的返回值将是一个以所创建的 mock 的名称为键的字典:
with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values:
assert 'other' in repr(values['other'])
assert 'thing' in repr(values['thing'])
assert values['thing'] is thing
assert values['other'] is other
所有补丁对象都具有 start() 和 stop() 方法。 使用这些方法可以更简单地在 setUp 方法上打补丁,还可以让你不必嵌套使用装饰器或 with 语句就能打多重补丁。
要使用这些方法请按正常方式调用 patch(), patch.object() 或 patch.dict() 并保留一个指向所返回 patcher 对象的引用。 然后你可以调用 start() 来原地打补丁并调用 stop() 来恢复它。
如果你使用 patch() 来创建自己的 mock 那么将可通过调用 patcher.start 来返回它。
patcher = patch('package.module.ClassName')
from package import module
original = module.ClassName
new_mock = patcher.start()
assert module.ClassName is not original
assert module.ClassName is new_mock
patcher.stop()
assert module.ClassName is original
assert module.ClassName is not new_mock
此操作的一个典型应用场景是在一个 TestCase 的 setUp 方法中执行多重补丁:
class MyTest(unittest.TestCase):
def setUp(self):
self.patcher1 = patch('package.module.Class1')
self.patcher2 = patch('package.module.Class2')
self.MockClass1 = self.patcher1.start()
self.MockClass2 = self.patcher2.start()
def tearDown(self):
self.patcher1.stop()
self.patcher2.stop()
def test_something(self):
assert package.module.Class1 is self.MockClass1
assert package.module.Class2 is self.MockClass2
MyTest('test_something').run()
小心 如果你要使用这个技巧则你必须通过调用 stop 来确保补丁被“恢复”。 这可能要比你想像的更麻烦,因为如果在 setUp 中引发了异常那么 tearDown 将不会被调用。 unittest.TestCase.addCleanup() 可以简化此操作:
class MyTest(unittest.TestCase):
def setUp(self):
patcher = patch('package.module.Class')
self.MockClass = patcher.start()
self.addCleanup(patcher.stop)
def test_something(self):
assert package.module.Class is self.MockClass
patch.stopall() 停止所有激活的补丁。 仅会停止通过 start 启动的补丁。
你可以为一个模块中的任何内置函数打补丁。 以以示例是为内置函数 ord() 打补丁:
@patch('__main__.ord')
def test(mock_ord):
mock_ord.return_value = 101
print(ord('c'))
所有补丁都可被用作类装饰器。 当以这种方式使用时它们将会包装类中的每个测试方法。 补丁会将以名字以 ‘test’ 开头的方法识别为测试方法。 这与 unittest.TestLoader 查找测试方法的默认方式相同。
你可能会想要为你的测试使用不同的前缀。 你可以通过设置 patch.TEST_PREFIX 来告知打补丁方不同的前缀:
patch.TEST_PREFIX = 'foo'
value = 3
@patch('__main__.value', 'not three')
class Thing:
def foo_one(self):
print(value)
def foo_two(self):
print(value)
如果你想要应用多重补丁那么你可以简单地堆叠多个装饰器。 请注意 装饰器是从下往上被应用的。 这是 Python 应用装饰器的标准方式。 被创建并传入你的测试函数的 mock 的顺序也将匹配这个顺序。
@patch.object(SomeClass, 'class_method')
@patch.object(SomeClass, 'static_method')
def test(mock1, mock2):
assert SomeClass.static_method is mock1
assert SomeClass.class_method is mock2
SomeClass.static_method('foo')
SomeClass.class_method('bar')
return mock1, mock2
mock1, mock2 = test()
mock1.assert_called_once_with('foo')
mock2.assert_called_once_with('bar')
patch() 通过(临时性地)修改某一个对象的 名称 指向另一个对象来发挥作用。 可以有多个名称指向任意单独对象,因此要让补丁起作用你必须确保已为被测试的系统所使用的名称打上补丁。
基本原则是你要在对象 被查找 的地方打补丁,这不一定就是它被定义的地方。 一组示例将有助于厘清这一点。
想像我们有一个想要测试的具有如下结构的项目:
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
现在我们要测试 some_function 但我们想使用 patch() 来模拟 SomeClass。 问题在于当我们导入模块 b 时,我们将必须让它从模块 a 导入 SomeClass。 如果我们使用 patch() 来模拟 a.SomeClass 那么它将不会对我们的测试造成影响;模块 b 已经拥有对 真正的 SomeClass 的引用因此看上去我们的补丁不会有任何影响。
关键在于对 SomeClass 打补丁操作是在它被使用(或它被查找)的地方。 在此情况下实际上 some_function 将在模块 b 中查找 SomeClass,而我们已经在那里导入了它。 补丁看上去应该是这样:
@patch('b.SomeClass')
但是,再考虑另一个场景,其中不是 from a import SomeClass 而是模块 b 执行了 import a 并且 some_function 使用了 a.SomeClass。 这两个导入形式都很常见。 在这种情况下我们要打补丁的类将在该模块中被查找因而我们必须改为对 a.SomeClass 打补丁:
@patch('a.SomeClass')
Mock 支持模拟 Python 协议方法,或称“魔术方法”。 这允许 mock 对象替代容器或其他实现了 Python 协议的对象。
因为查找魔术方法的方式不同于普通方法 [2],这种支持采用了特别的实现。 这意味着只有特定的魔术方法受到支持。 受支持的是 几乎 所有魔术方法。
对魔术方法的调用不会在 method_calls 中出现,但它们会被记录在 mock_calls 中。
你可以通过在某个函数或 mock 实例中设置魔术方法来模拟它们。 如果你是使用函数则它 必须 接受 self 作为第一个参数。
def __str__(self):
return 'fooble'
mock = Mock()
mock.__str__ = __str__
str(mock)
'fooble'
mock = Mock()
mock.__str__ = Mock()
mock.__str__.return_value = 'fooble'
str(mock)
'fooble'
mock = Mock()
mock.__iter__ = Mock(return_value=iter([]))
list(mock)
[]
一个这样的应用场景是在 with 语句中模拟作为上下文管理器的对象:
mock = Mock()
mock.__enter__ = Mock(return_value='foo')
mock.__exit__ = Mock(return_value=False)
with mock as m:
assert m == 'foo'
mock.__enter__.assert_called_with()
mock.__exit__.assert_called_with(None, None, None)
受支持魔术方法的完整列表如下:
__hash__, __sizeof__, __repr__ 和 __str__
__dir__, __format__ 和 __subclasses__
__round__, __floor__, __trunc__ 和 __ceil__
比较运算: __lt__, __gt__, __le__, __ge__, __eq__ 和 __ne__
容器方法: __getitem__, __setitem__, __delitem__, __contains__, __len__, __iter__, __reversed__ 和 __missing__
上下文管理器: __enter__, __exit__, __aenter__ 和 __aexit__
单目数值运算方法: __neg__, __pos__ 和 __invert__
数字方法(包括靠右操作符(__radd__等)和扩展算术赋值(__iadd__等)): __add__, __sub__, __mul__, __matmul__, __truediv__, __floordiv__, __mod__, __divmod__, __lshift__, __rshift__, __and__, __xor__, __or__, and __pow__
数值转换方法: __complex__, __int__, __float__ 和 __index__
描述器方法: __get__, __set__ and __delete__
封存方法: __reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate__ 和 __setstate__
文件系统路径表示: __fspath__
异步迭代方法: __aiter__ and __anext__
下列方法均存在但是 不受 支持,因为它们或者被 mock 所使用,或者无法动态设置,或者可能导致问题:
__getattr__, __setattr__, __init__ 和 __new__
__prepare__, __instancecheck__, __subclasscheck__, __del__
MagicMock 是包含了大部分魔术方法的默认实现的 Mock 的子类。 你可以使用 MagicMock 而无须自行配置魔术方法。
构造器形参的含义与 Mock 的相同。
如果你使用了 spec 或 spec_set 参数则将 只有 存在于 spec 中的魔术方法会被创建。
魔术方法是通过 MagicMock 对象来设置的,因此你可以用通常的方式来配置它们并使用它们:
mock = MagicMock()
mock[3] = 'fish'
mock.__setitem__.assert_called_with(3, 'fish')
mock.__getitem__.return_value = 'result'
mock[2]
'result'
在默认情况下许多协议方法都需要返回特定类型的对象。 这些方法都预先配置了默认的返回值,以便它们在你对返回值不感兴趣时可以不做任何事就能被使用。 如果你想要修改默认值则你仍然可以手动 设置 返回值。
方法及其默认返回值:
__lt__: NotImplemented
__gt__: NotImplemented
__le__: NotImplemented
__ge__: NotImplemented
__int__: 1
__contains__: False
__len__: 0
__iter__: iter([])
__exit__: False
__aexit__: False
__complex__: 1j
__float__: 1.0
__bool__: True
__index__: 1
__hash__: mock 的默认 hash
__str__: mock 的默认 str
__sizeof__: mock 的默认 sizeof
在 MagicMock 中受到支持但默认未被设置的魔术方法有:
__subclasses__
__dir__
__format__
__get__, __set__ 和 __delete__
__reversed__ 和 __missing__
__reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate__ 和 __setstate__
__getformat__
sentinel 对象提供了一种为你的测试提供独特对象的便捷方式。
属性是在你通过名称访问它们时按需创建的。 访问相同的属性将始终返回相同的对象。 返回的对象会有一个合理的 repr 以使测试失败消息易于理解。
在测试时你可能需要测试是否有一个特定的对象作为参数被传给了另一个方法,或是被其返回。 通常的做法是创建一个指定名称的 sentinel 对象来执行这种测试。 sentinel 提供了一种创建和测试此类对象的标识的便捷方式。
real = ProductionClass()
real.method = Mock(name="method")
real.method.return_value = sentinel.some_object
result = real.method()
assert result is sentinel.some_object
result
sentinel.some_object
call() 是一个可创建更简单断言的辅助对象,用于同 call_args, call_args_list, mock_calls 和 method_calls 进行比较。 call() 也可配合 assert_has_calls() 使用。
call.call_list()
对于代表多个调用的 call 对象,call_list() 将返回一个包含所有中间调用以及最终调用的列表。
call_list 特别适用于创建针对“链式调用”的断言。 链式调用是指在一行代码中执行的多个调用。 这将使得一个 mock 的中存在多个条目 mock_calls。 手动构造调用的序列将会很烦琐。
m = MagicMock()
m(1).method(arg='foo').other('bar')(2.0)
kall = call(1).method(arg='foo').other('bar')(2.0)
kall.call_list()
[call(1),
call().method(arg='foo'),
call().method().other('bar'),
call().method().other()(2.0)]
m.mock_calls == kall.call_list()
True
根据构造方式的不同,call 对象可以是一个 (位置参数, 关键字参数) 或 (名称, 位置参数, 关键字参数) 元组。 当你自行构造它们时这没有什么关系,但是 Mock.call_args, Mock.call_args_list 和 Mock.mock_calls 属性当中的 call 对象可以被反查以获取它们所包含的单个参数。
Mock.call_args 和 Mock.call_args_list 中的 call 对象是 (位置参数, 关键字参数) 二元组而 Mock.mock_calls 中以及你自行构造的 call 对象则是 (名称, 位置参数, 关键字参数) 三元组。
你可以使用它们作为“元组”的特性来为更复杂的内省和断言功能获取单个参数。 位置参数是一个元组(如无位置参数则为空元组)而关键字参数是一个字典:
m = MagicMock(return_value=None)
m(1, 2, 3, arg='one', arg2='two')
kall = m.call_args
kall.args
(1, 2, 3)
kall.kwargs
{'arg': 'one', 'arg2': 'two'}
kall.args is kall[0]
True
kall.kwargs is kall[1]
True
m = MagicMock()
m.foo(4, 5, 6, arg='two', arg2='three')
kall = m.mock_calls[0]
name, args, kwargs = kall
name
'foo'
args
(4, 5, 6)
kwargs
{'arg': 'two', 'arg2': 'three'}
name is m.mock_calls[0][0]
True
有时你可能需要设置关于要模拟的调用中的 某些 参数的断言,但是又不想理会其他参数或者想要从 call_args 中单独拿出它们并针对它们设置更复杂的断言。
为了忽略某些参数你可以传入与 任意对象 相等的对象。 这样再调用 assert_called_with() 和 assert_called_once_with() 时无论传入什么参数都将执行成功。
mock = Mock(return_value=None)
mock('foo', bar=object())
mock.assert_called_once_with('foo', bar=ANY)
ANY 也可被用在与 mock_calls 这样的调用列表相比较的场合:
m = MagicMock(return_value=None)
m(1)
m(1, 2)
m(object())
m.mock_calls == [call(1), call(1, 2), ANY]
True
FILTER_DIR是一个模块级变量,用于控制mock对象响应DIR()的方式。默认值为True,它使用下面描述的筛选,仅显示有用的成员。如果您不喜欢此筛选,或者出于诊断目的需要将其关闭,请将mock.FILTER_DIR设置为False
当启用过滤时,dir(some_mock) 将只显示有用的属性并将包括正常情况下不会被显示的任何动态创建的属性。 如果 mock 是使用 spec (当然也可以是 autospec) 创建的则原有的所有属性都将被显示,即使它们还未被访问过:
dir(Mock())
['assert_any_call',
'assert_called',
'assert_called_once',
'assert_called_once_with',
'assert_called_with',
'assert_has_calls',
'assert_not_called',
'attach_mock',
...
from urllib import request
dir(Mock(spec=request))
['AbstractBasicAuthHandler',
'AbstractDigestAuthHandler',
'AbstractHTTPHandler',
'BaseHandler',
...
许多不太有用的(是 Mock 的而不是被模拟对象的私有成员)开头带有下划线和双下划线的属性已从在 Mock 上对 dir() 的调用的结果中被过滤。 如果你不喜欢此行为你可以通过设置模块级开关 FILTER_DIR 来将其关闭:
from unittest import mock
mock.FILTER_DIR = False
dir(mock.Mock())
['_NonCallableMock__get_return_value',
'_NonCallableMock__get_side_effect',
'_NonCallableMock__return_value_doc',
'_NonCallableMock__set_return_value',
'_NonCallableMock__set_side_effect',
'__call__',
'__class__',
...
或者,您可以只使用vars(my_mock)(实例成员)和dir(type(my_mock))(类型成员)来绕过过滤,而不考虑mock.FILTER_DIR。
创建 mock 来代替使用 open() 的辅助函数。 它对于 open() 被直接调用或被用作上下文管理器的情况都适用。
mock 参数是要配置的 mock 对象。 如为 None (默认值) 则将为你创建一个 MagicMock,其 API 会被限制为可用于标准文件处理的方法或属性。
read_data 是供文件处理的 read(), readline() 和 readlines() 方法返回的字符串。 调用这些方法将会从 read_data 获取数据直到它被耗尽。 对这些方法的模拟是相当简化的:每次 mock 被调用时,read_data 都将从头开始。 如果你需要对你提供给测试代码的数据有更多控制那么你将需要自行定制这个 mock。 如果这还不够用,那么通过一些 PyPI 上的内存文件系统包可以提供更真实的测试文件系统。
with patch('__main__.open', mock_open(read_data='bibble')) as m:
with open('foo') as h:
result = h.read()
m.assert_called_once_with('foo')
assert result == 'bibble'
自动 spec 是基于现有 mock 的 spec 特性。 它将 mock 的 api 限制为原始对象 (spec) 的 api,但它是递归(惰性实现)的因而 mock 的属性只有与 spec 的属性相同的 api。 除此之外被模拟的函数 / 方法具有与原对应物相同的调用签名因此如果它们被不正确地调用时会引发 TypeError。
你可以将 autospec=True 传给 patch() / patch.object() 或是使用 create_autospec() 函数来创建带有 spec 的 mock。 如果你是将 autospec=True 参数传给 patch() 那么被替代的那个对象将被用作 spec 对象。 因为 spec 控制是“惰性地”执行的(spec 在 mock 中的属性被访问时才会被创建)所以即使是非常复杂或深度嵌套的对象(例如需要导入本身已导入了多个模块的模块)你也可以使用它而不会有太大的性能损失。
在许多情况下你将只需将 autospec=True 添加到你现有的 patch() 调用中即可防止拼写错误和 api 变化所导致的问题。
缺点和限制:为了知道在 spec 对象上有哪些属性是可用的,autospec 必须对 spec 进行自省(访问其属性)。 当你遍历 mock 上的属性时在原始对象上的对应遍历也将在底层进行。 如果你的任何带 spec 的对象具有可触发代码执行的特征属性或描述器则你可能会无法使用 autospec。
在另一方面更好的的做法是将你的对象设计为可以安全地执行自省
一个更严重的问题在于实例属性往往是在__init__()方法中被创建而在类中完全不存在。 autospec 无法获取动态创建的属性而使得 api 被限制于可见的属性。
要解决这个问题有几种不同的方式。 最容易但多少有些烦扰的方式是简单地在 mock 创建完成后再设置所需的属性。
with patch('__main__.Something', autospec=True):
thing = Something()
thing.a = 33
spec 和 autospec 都有更严格的版本 确实能 阻止你设置不存在的属性。 这在你希望确保你的代码只能 设置 有效的属性时也很有用,但显然它会阻止下面这个特定的应用场景。
with patch('__main__.Something', autospec=True, spec_set=True):
thing = Something()
thing.a = 33
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
解决此问题的最好方式可能是添加类属性作为在__init__()中初始化的实例属性的默认值。 请注意如果你只在__init__()中设置默认属性那么通过类属性来提供它们(当然会在实例之间共享)也将有更快的速度。 例如
class Something:
a = 33
如果你不喜欢修改你的生产类来添加默认值那么还有其他的选项。 其中之一是简单地使用一个实例而非类作为 spec。 另一选项则是创建一个生产类的子类并向该子类添加默认值而不影响到生产类。 这两个选项都需要你使用一个替代对象作为 spec。
class Something:
def __init__(self):
self.a = 33
class SomethingForTest(Something):
a = 33
p = patch('__main__.Something', autospec=SomethingForTest)
mock = p.start()
mock.a
封包将在访问被封包的 mock 的属性或其任何已经被递归地模拟的属性时禁止自动创建 mock。
如果一个带有名称或 spec 的 mock 实例被分配给一个属性则将不会在封包链中处理它。 这可以防止人们对 mock 对象的固定部分执行封包。
mock = Mock()
mock.submock.attribute1 = 2
mock.not_submock = mock.Mock(name="sample_name")
seal(mock)
mock.new_attribute # This will raise AttributeError.
mock.submock.attribute2 # This will raise AttributeError.
mock.not_submock.attribute2 # This won't raise.
Parameterized 是 Python 的一个参数化库,同时支持 unittest、Nose 和 pytest 单元测试框架。
GitHub 地址:Parameterized
Parameterized 支持 pip 安装。
pip install parameterized
@parameterized和@parameterized.expand装饰器接受元组或param(…)的列表或可迭代项,或返回列表或可遍历项的可调用项
当使用迭代器或生成器时,所有项目都将在测试运行开始前加载到内存中(我们这样做是为了确保生成器在多进程或多线程测试环境中只用完一次)。
from parameterized import parameterized, param
# A list of tuples
@parameterized([
(2, 3, 5),
(3, 5, 8),
])
def test_add(a, b, expected):
assert_equal(a + b, expected)
# A list of params
@parameterized([
param("10", 10),
param("10", 16, base=16),
])
def test_int(str_val, expected, base=10):
assert_equal(int(str_val, base=base), expected)
# An iterable of params
@parameterized(
param.explicit(*json.loads(line))
for line in open("testcases.jsons")
)
def test_from_json_file(...):
...
# A callable which returns a list of tuples
def load_test_cases():
return [
("test1", ),
("test2", ),
]
@parameterized(load_test_cases)
def test_from_function(name):
...
@parameterized装饰器可以用于测试类方法和独立函数,但是不能用于unittest.TestCase的子类里
from parameterized import parameterized
class AddTest(object):
@parameterized([
(2, 3, 5),
])
def test_add(self, a, b, expected):
assert_equal(a + b, expected)
@parameterized([
(2, 3, 5),
])
def test_add(a, b, expected):
assert_equal(a + b, expected)
在无法使用测试生成器的情况下(例如,当测试类是unittest.TestCase的子类时),可以使用@parameterized.expand生成测试方法
请注意,@parameterized.expand通过在测试类上创建新方法来工作。如果第一个参数是字符串,那么该字符串将被添加到方法名称的末尾。
import unittest
from parameterized import parameterized
class AddTestCase(unittest.TestCase):
@parameterized.expand([
("2 and 3", 2, 3, 5),
("3 and 5", 3, 5, 8),
])
def test_add(self, _, a, b, expected):
assert_equal(a + b, expected)
# 创建的测试用例
test_add_0_2_and_3 (example.AddTestCase) ... ok
test_add_1_3_and_5 (example.AddTestCase) ... ok
@parameterized.expand生成的测试用例的名称可以使用name_func关键字参数进行自定义。该值应该是一个接受三个参数的函数:testcase_func、param_num和params,并且应该返回测试用例的名称。testcase_func将是要测试的函数,param_num将是参数列表中测试用例参数的索引,param(param的实例)将是将要使用的参数。
import unittest
from parameterized import parameterized
def custom_name_func(testcase_func, param_num, param):
return "%s_%s" %(
testcase_func.__name__,
parameterized.to_safe_name("_".join(str(x) for x in param.args)),
)
class AddTestCase(unittest.TestCase):
@parameterized.expand([
(1, 2, 3),
(2, 3, 5),
], name_func=custom_name_func)
def test_add(self, a, b, expected):
assert_equal(a + b, expected)
# 创建的测试用例
test_add_1_2_3 (example.AddTestCase) ... ok
test_add_2_3_5 (example.AddTestCase) ... ok
param(…)helper类存储一个特定测试用例的参数。它可以用于将关键字参数传递给测试用例
from parameterized import parameterized, param
@parameterized([
param("10", 10),
param("10", 16, base=16),
])
def test_int(str_val, expected, base=10):
assert_equal(int(str_val, base=base), expected)
如果测试用例有一个文档字符串,那么该测试用例的参数将被附加到文档字符串的第一行。可以使用doc_func参数控制此行为
from parameterized import parameterized
@parameterized([
(1, 2, 3),
(4, 5, 9),
])
def test_add(a, b, expected):
""" Test addition. """
assert_equal(a + b, expected)
def my_doc_func(func, num, param):
return "%s: %s with %s" %(num, func.__name__, param)
@parameterized([
(5, 4, 1),
(9, 6, 3),
], doc_func=my_doc_func)
def test_subtraction(a, b, expected):
assert_equal(a - b, expected)
# 创建的测试用例
Test addition. [with a=1, b=2, expected=3] ... ok
Test addition. [with a=4, b=5, expected=9] ... ok
0: test_subtraction with param(*(5, 4, 1)) ... ok
1: test_subtraction with param(*(9, 6, 3)) ... ok
@parameterized_class使用属性列表或将应用于类的dict列表来参数化整个类
from yourapp.models import User
from parameterized import parameterized_class
@parameterized_class([
{ "username": "user_1", "access_level": 1 },
{ "username": "user_2", "access_level": 2, "expected_status_code": 404 },
])
class TestUserAccessLevel(TestCase):
expected_status_code = 200
def setUp(self):
self.client.force_login(User.objects.get(username=self.username)[0])
def test_url_a(self):
response = self.client.get('/url')
self.assertEqual(response.status_code, self.expected_status_code)
def tearDown(self):
self.client.logout()
@parameterized_class(("username", "access_level", "expected_status_code"), [
("user_1", 1, 200),
("user_2", 2, 404)
])
class TestUserAccessLevel(TestCase):
def setUp(self):
self.client.force_login(User.objects.get(username=self.username)[0])
def test_url_a(self):
response = self.client.get("/url")
self.assertEqual(response.status_code, self.expected_status_code)
def tearDown(self):
self.client.logout()
@parameterized_class装饰器接受class_name_func参数,该参数控制@parameterized_class生成的参数化类的名称
from parameterized import parameterized, parameterized_class
def get_class_name(cls, num, params_dict):
# By default the generated class named includes either the "name"
# parameter (if present), or the first string value. This example shows
# multiple parameters being included in the generated class name:
return "%s_%s_%s%s" %(
cls.__name__,
num,
parameterized.to_safe_name(params_dict['a']),
parameterized.to_safe_name(params_dict['b']),
)
@parameterized_class([
{ "a": "hello", "b": " world!", "expected": "hello world!" },
{ "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
], class_name_func=get_class_name)
class TestConcatenation(TestCase):
def test_concat(self):
self.assertEqual(self.a + self.b, self.expected)
使用单一参数
如果一个测试函数只接受一个参数,并且该值不可迭代,那么可以提供一个值列表,而无需将每个值包装在元组中
但是,如果单个参数是可迭代的(如列表或元组),则必须将其包装在元组、列表或param(…)helper中
@parameterized([1, 2, 3])
def test_greater_than_zero(value):
assert value > 0
@parameterized([
([1, 2, 3], ),
([3, 3], ),
([6], ),
])
def test_sums_to_6(numbers):
assert sum(numbers) == 6
可以与mock.patch一起使用,但是参数顺序可能会令人困惑。@mock.patch(…)装饰器必须位于@parameterized(…)之下,而mocked参数必须位于最后。使用@parameterized.expand时也是如此。
@mock.patch("os.getpid")
class TestOS(object):
@parameterized(...)
@mock.patch("os.fdopen")
@mock.patch("os.umask")
def test_method(self, param1, param2, ..., mock_umask, mock_fdopen, mock_getpid):