unittest的基本用法

前言

本篇文章就来说一说python的内置单元测试库:unittest库。


回顾单元测试的定义

单元测试是指在计算机编程中,针对程序模块来进行正确性检查的测试工作。单元测试具有以下特点:

  • 程序单元是应用最小的可测试部件,通常基于类或者类的方法去进行测试。
  • 程序单元和其他单元式互相独立的。
  • 单元测试的执行速度极快。
  • 单元测试发现问题,更容易定位。
  • 单元测试通常由开发人员来完成。
  • 通过了解代码的实现逻辑进行测试,通常称之为白盒测试。

unittest单元测试框架

python语言中有很多的单元测试框架和工具,比如pytest和nosetest。但是unittest单元测试框架作为python的标准库,是其他单元测试框架的基础,一般python中都会自带该模块。对于自动化测试来说,掌握unittest单元测试就显得尤为重要。

unittest的定义

unittest被称为python版本的junit,主要用于python程序的单元测试。

unittest拥有支持自动化测试,测试用例之间共享setUp(测试前置)tearDown(测试后置)代码块,集合所有的测试用例并将测试结果独立的展示在报告框架的特性。


unittest中的四个重要概念

官方文档中给出了unittest中4个非常重要的概念:

  • test fixture——测试固件

    一个test fixture代表一个或多个测试执行前的准备工作和测试结束后的清理动作。例如:创建数据库连接、启动服务进程、测试环境清理、关闭数据库连接等。

  • test case——测试用例

    一个test case就是一个最小的测试单元,也就是一个完整的测试流程。针对一组特殊的输入进行特殊的验证和响应。通过集成unittest提供的测试类TestCase,可以创建测试用例。

  • test suite——测试套件

    一个test suite就是一组测试用例,一组测试套件或者两者组成的集合。它的作用就是把测试用例集合在一起,然后一次性执行集合中所有的测试用例。

  • test runner——测试运行器

    一个test runner由执行设定的测试用例和将测试结果提供给用户的两部分功能组成。


单元测试加载方法

在单元测试中,提供了两种加载测试用例的方法:

  • 直接通过unittest.main()方法加载单元测试的测试模块,这是一种最简单的加载方法。所有的测试用例的执行顺序都是按照方法名的字符串表示的ASCII码升序排序。
  • 将所有的单元测试用例TestCase加载到测试套件Test Suite集合中,然后一次性加载所有测试对象。

测试用例

unitest通过TestCase来构建测试用例,并要求所有的测试类都必须继承该类,它是所有测试用例的基类。TestCase的子类会继承几个特殊方法:

  • setUp():每个测试方法运行前运行,测试前置的初始化工作。
  • tearDown():每个测试方法结束后运行,测试后的清理工作。
  • setUpClass():所有的测试方法运行前运行,单元测试类运行前的准备工作。必须使用@classmethod装饰器进行装饰。整个测试类运行过程中只会执行一次。
  • tearDownClass():所有的测试方法结束后运行,单元测试类运行后的清理工作。必须使用@classmethod装饰器进行装饰。整个测试类运行过程中只会执行一次。

先来看一个简单的示例:

import unittest


class TestDemo(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        print("测试类前置方法...")

    @classmethod
    def tearDownClass(cls) -> None:
        print("测试类后置方法...")

    def setUp(self) -> None:
        print("测试方法前置方法...")

    def tearDown(self) -> None:
        print("测试方法后置方法...")

    def test_1(self):
        print("我去测试一下...")

    def test_2(self):
        print("我去测试两下...")


if __name__ == "__main__":
    unittest.main()

执行结果

测试类前置方法...
测试方法前置方法...
我去测试一下...
测试方法后置方法...
.测试方法前置方法...
我去测试两下...
测试方法后置方法...
.测试类后置方法...

----------------------------------------------------------------------
Ran 2 tests in 0.012s

OK

可以看到测试类的前置后置,分别在开始和结束分别被执行了一次,而后是测试方法的前置后置方法包裹着测试方法执行了两次。

同时可以看到里面显示了两个点.,代表了运行成功。同时运行失败的标识为F,运行错误的标识为E。改造一下代码来看看。

import unittest


class TestDemo(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        print("测试类前置方法...")

    @classmethod
    def tearDownClass(cls) -> None:
        print("测试类后置方法...")

    def setUp(self) -> None:
        print("测试方法前置方法...")

    def tearDown(self) -> None:
        print("测试方法后置方法...")

    def test_1(self):
        print("我去测试一下...")
        raise Exception('error')

    def test_2(self):
        print("我去测试两下...")
        assert 1==2

if __name__ == "__main__":
    unittest.main()

运行结果:

测试类前置方法...
测试方法前置方法...
我去测试一下...
测试方法后置方法...
E测试方法前置方法...
我去测试两下...
测试方法后置方法...
F测试类后置方法...

======================================================================
ERROR: test_1 (__main__.TestDemo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:/Users/hoou/PycharmProjects/web-unittest/demo/demo.py", line 22, in test_1
    raise Exception('error')
Exception: error

======================================================================
FAIL: test_2 (__main__.TestDemo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:/Users/hoou/PycharmProjects/web-unittest/demo/demo.py", line 26, in test_2
    assert 1==2
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.003s

FAILED (failures=1, errors=1)

可以看到运行的结果一个是错误的为E,一个是失败的为F,并且分别显示出了各自的报错信息及原由。


测试集合

加载测试类执行
import unittest


class TestDemo(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        print("测试类前置方法...")

    @classmethod
    def tearDownClass(cls) -> None:
        print("测试类后置方法...")

    def setUp(self) -> None:
        print("测试方法前置方法...")

    def tearDown(self) -> None:
        print("测试方法后置方法...")

    def test_1(self):
        print("我去测试一下...")

    def test_2(self):
        print("我去测试两下...")


class Test001(unittest.TestCase):
    def test_1(self):
        print("1+1...", 2)

    def test_2(self):
        print("2+2...", 4)


if __name__ == "__main__":
    testcase1 = unittest.TestLoader().loadTestsFromTestCase(TestDemo, )
    testcase2 = unittest.TestLoader().loadTestsFromTestCase(Test001)
    suite = unittest.TestSuite([testcase1, testcase2])
    unittest.TextTestRunner(verbosity=2).run(suite)

执行结果:

Ran 4 tests in 0.012s

OK

C:UsershoouPycharmProjectsweb-unittest>c:/Users/hoou/PycharmProjects/web-unittest/venv/Scripts/python.exe c:/Users/hoou/PycharmProjects/web-unittest/demo/demo.py
测试类前置方法...
test_1 (__main__.TestDemo) ... 测试方法前置方法...
我去测试一下...
测试方法后置方法...
ok
test_2 (__main__.TestDemo) ... 测试方法前置方法...
我去测试两下...
测试方法后置方法...
ok
测试类后置方法...
test_1 (__main__.Test001) ... 1+1... 2
ok
test_2 (__main__.Test001) ... 2+2... 4
ok

----------------------------------------------------------------------
Ran 4 tests in 0.015s

OK

代码解释:

  • TestLoader类: 测试用例加载器,返回一个测试用例集合。

  • loadTestsFromTestCase类:根据给定的测试类,获取所有test开头的测试方法,返回一个测试集合。

  • TestSuite类:组装所有的测试用例实例,支持添加和删除测试用例,最后传递给runner进行测试执行。

  • TextTestRunner类:测试用例执行类,其中Text代表以文本的方式输出测试结果。

verbosity参数说明:

  • 设置verbosity<=0的数字,表示不提示执行成功的用例数。
  • 设置verbosity=1的数字,表示结果中仅以.表示执行成功的用例数。
  • 设置verbosity>=2的数字,可以输出每个用例的执行的详细信息

按照特定的顺序执行测试用例(加载测试方法的方式)
import unittest


class Test001(unittest.TestCase):
    def test_1(self):
        print("1+1...", 2)

    def test_2(self):
        print("2+2...", 4)

    def test_3(self):
        print("3+3...", 6)


if __name__ == "__main__":
    suite = unittest.TestSuite()
    suite.addTest(Test001('test_3'))
    suite.addTest(Test001('test_2'))
    suite.addTest(Test001('test_1'))
    runner = unittest.TextTestRunner()
    runner.run(suite)

执行结果:

C:UsershoouPycharmProjectsweb-unittest>python c:/Users/hoou/PycharmProjects/web-unittest/demo/demo.py
3+3... 6
.2+2... 4
.1+1... 2
.
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

C:UsershoouPycharmProjectsweb-unittest>

可以看到,测试用例已经按照我们设定的顺序在执行了。


忽略某个测试方法

import sys
import unittest


class Test001(unittest.TestCase):
    @unittest.expectedFailure  # 即使报错了,也会被计为成功的用例
    def test_1(self):
        print("1+1...", 2)
        assert 1 + 1 == 3

    @unittest.skip('无条件的忽略')  # 不管什么情况都会进行忽略
    def test_2(self):
        print("2+2...", 4)

    @unittest.skipIf(sys.platform == "win32", "跳过")  # 如果系统平台为window则忽略
    def test_3(self):
        print("3+3...", 6)

    @unittest.skipUnless(sys.platform == "win32", "跳过")  # 跳过该用例,除非系统平台为window
    def test_4(self):
        print("4+4...", 8)

    def test_5(self):
        print("5+5...", 10)


if __name__ == "__main__":
    unittest.main(verbosity=2)

执行结果:

test_1 (__main__.Test001) ... 1+1... 2
expected failure
test_2 (__main__.Test001) ... skipped '无条件的忽略'
test_3 (__main__.Test001) ... skipped '跳过'
test_4 (__main__.Test001) ... 4+4... 8
ok
test_5 (__main__.Test001) ... 5+5... 10
ok

----------------------------------------------------------------------
Ran 5 tests in 0.005s

OK (skipped=2, expected failures=1)

可以看到skip有两个分别是test_2和test_3,因为他们的忽略条件都成立了。


命令行模式运行测试用例

我们要运行的代码片段:

import sys
import unittest


class Test001(unittest.TestCase):
    @unittest.expectedFailure  # 即使报错了,也会被计为成功的用例
    def test_1(self):
        print("1+1...", 2)
        assert 1 + 1 == 3

    @unittest.skip('无条件的忽略')  # 不管什么情况都会进行忽略
    def test_2(self):
        print("2+2...", 4)

    @unittest.skipIf(sys.platform == "win32", "跳过")  # 如果系统平台为window则忽略
    def test_3(self):
        print("3+3...", 6)

    @unittest.skipUnless(sys.platform == "win32", "跳过")  # 跳过该用例,除非系统平台为window
    def test_4(self):
        print("4+4...", 8)

    def test_5(self):
        print("5+5...", 10)


if __name__ == "__main__":
    unittest.main(verbosity=2)

  • 直接运行整个测试模块

    首先我们需要先cd进入我们需要运行的测试模块目录中

    执行命令:

    C:UsershoouPycharmProjectsweb-unittestdemo>python -m unittest testdemo
    1+1... 2
    xss4+4... 8
    .5+5... 10
    .
    ----------------------------------------------------------------------
    Ran 5 tests in 0.002s
    
    OK (skipped=2, expected failures=1)
    

    我们可以看到成功运行了。

  • 执行测试模块中的某个类

    C:UsershoouPycharmProjectsweb-unittestdemo>python -m unittest testdemo.Test001
    1+1... 2
    xss4+4... 8
    .5+5... 10
    .
    ----------------------------------------------------------------------
    Ran 5 tests in 0.008s
    
    OK (skipped=2, expected failures=1)
    
  • 执行测试模块中的某个测试方法

    C:UsershoouPycharmProjectsweb-unittestdemo>python -m unittest testdemo.Test001.test_5
    5+5... 10
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK
    
    

批量执行测试模块

unitest单元测试框架提供的批量执行测试模块的方法,官方称之为测试发现。即可以自动发现测试用例,并执行目录下满足规则的测试模块。为了更好的发现测试用例,文件名必须以test开头,测试类和测试方法也必须以test开头。

测试发现的执行模式分为两种:

  • 程序文件模式

    import sys
    import unittest
    
    
    class Test001(unittest.TestCase):
        @unittest.expectedFailure  # 即使报错了,也会被计为成功的用例
        def test_1(self):
            print("1+1...", 2)
            assert 1 + 1 == 3
    
        @unittest.skip('无条件的忽略')  # 不管什么情况都会进行忽略
        def test_2(self):
            print("2+2...", 4)
    
        @unittest.skipIf(sys.platform == "win32", "跳过")  # 如果系统平台为window则忽略
        def test_3(self):
            print("3+3...", 6)
    
        @unittest.skipUnless(sys.platform == "win32", "跳过")  # 跳过该用例,除非系统平台为window
        def test_4(self):
            print("4+4...", 8)
    
        def test_5(self):
            print("5+5...", 10)
    
    
    if __name__ == "__main__":
        suite = unittest.TestLoader().discover('.')
        runner = unittest.TextTestRunner(verbosity=2)
        runner.run(suite)
    

    可以看到TestLoader类中提供了discover方法来发现测试方法,其中'.'代表了当前的文件。

  • 命令行模式

    C:UsershoouPycharmProjectsweb-unittestdemo>python -m unittest discover
    1+1... 2
    xss4+4... 8
    .5+5... 10
    .
    ----------------------------------------------------------------------
    Ran 5 tests in 0.009s
    
    OK (skipped=2, expected failures=1)
    

    执行当前目录下的测试脚本,通过unittest提供的discover命令。

    discover命令的一些参数:

    • -v / --verbose:输出详细的测试信息

    • -s / --start-directory:开始进行搜索的目录,默认为当前目录(.)

    • -p / --pattern:用于匹配测试文件的模式,默认为test**.py

    • -t / --top-level-directory:指定项目的最上层目录,通常为开始时所在的目录

    • -s-p-t 选项可以按顺序作为位置参数传入。以下两条命令是等价的:

      python -m unittest discover -s project_directory -p "*_test.py"
      python -m unittest discover project_directory "*_test.py"
      

后记

unittest里面的东西还不少,就算是参考资料也写的挺累的。后续在更新断言方法和HTML test runner吧,今天先到这里吧。

原文地址:https://www.cnblogs.com/wxhou/p/14071553.html