使用 Python 内置的单元测试框架

Python 2020-02-18 401 次浏览 次点赞

起步

随着项目不断变得庞大,复杂性越来越高。为了保证代码质量和可用性,可以将应用的最小部件来进行正确性的检测工作。因此就有了单元测试。单元测试带来了诸多的好处:提高代码质量;提高程序的健壮性;避免代码重构引入新的问题。

单元测试框架 unittest

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 使用简单,功能强大,日常的测试需求都能得到很好的满足。


本文由 hongweipeng 创作,采用 署名-非商业性使用-相同方式共享 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!