起步

  随着项目不断变得庞大,复杂性越来越高。为了保证代码质量和可用性,可以将应用的最小部件来进行正确性的检测工作。因此就有了单元测试。单元测试带来了诸多的好处:提高代码质量;提高程序的健壮性;避免代码重构引入新的问题。
  unittest 作为内置的标准库,它很优秀,由于受到 JUnit 的启发,因此它与其他语言单元测试的风格类似。
  测试文件代码结构
  官方文档:https://docs.python.org/zh-cn/3.9/library/unittest.html,以下是官方的例子,我们可以从中学会一个测试文件的代码结构:
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 类,需要进行测试的方法的函数名应该 test 开头,不以 test 开头的方法测试的时候不会被主动调用。测试的正确性关键是调用 assert..() 来检查预期的结果。框架提供了诸多的 assertxxx() 函数组,其中最常用的有三个:
  · assertEqual :用于判断两个值是否相等;
  · assertTrue/assertFalse 用于判断表达式是 True 还是 False;
  · assertRaises :用于检测异常。
  运行测试
  如果测试文件用有 unittest.main() ,那么它可以安装普通py文件一样命令行运行, eg: python filename.py :
...
-----------------------------------
Ran 3 tests in 0.000s
OK
  若在调用命令行运行时添加 -v 参数,则显示的信息会更详细, eg: python filename.py -v:
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok
-----------------------------------
Ran 3 tests in 0.001s
OK
  如果测试文件没有提供 unittest.main() ,那么可以采用 -m unittest 来执行要测试的模块,eg: python -m unittest filename 进行。
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    # 指定路径
  前置(setUp)和清理(tearDown)方法。
  setUp() 是在执行测试方法前的前置操作,用来完成测试前的准备工作,比如建立数据库连接,打开文件等。tearDown() 则是在测试方法都执行完毕后用来清理工作的,比如断开数据库连接,关闭文件等。
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
  若在 setUp() 方法中发生了异常,测试框架视为测试发生了错误,测试方法不会被执行。
  若 setUp() 运行成功,无论测试方法是否成功,都会运行 tearDown() 。
  跳过测试
  如果想跳过某测试用例暂,可以在该方法前加一个装饰器:
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
# 针对win平台进行的测试
@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_maybe_skipped (__main__.MyTestCase) ... skipped 'external resource not available'
test_windows_support (__main__.MyTestCase) ... skipped 'requires Windows'
----------------------------------------------------------------------
Ran 4 tests in 0.005s
OK (skipped=4)
  可以跳过整个测试类:
@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
def test_not_run(self):
pass
  使用 expectedFailure() 表明预期失败,这个常用在程序有已知的问题,但还找不到解决方案,就将测试用例写在单元测试中,以后再来解决这些问题:
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
  总结
  unittest 使用简单,功能强大,日常的测试需求都能得到很好的满足。