欢迎来到赛兔子家园

unittest单元测试框架

概述

unittest是Python内置的单元测试框架,不仅可以完成单元测试,也适用于自动化测试中。

unittest提供了丰富的断言方法,判断测试用例是否通过,然后生成测试结果报告。

官网帮助文档:https://docs.python.org/zh-cn/3/library/unittest.html

单元测试框架主要用来完成以下三件事:

  • 提供用例组织与执行:当测试用例只有几条时,可以不必考虑用例的组织,但是当用例达到成百上千条时,大量的用例堆砌在一起,就产生了扩展性与维护性等,单元测试框架就是用来解决用例的规范与组织问题。
  • 提供了丰富的比较方法:不论是功能测试,还是单元测试,在用例执行完成之后都需要将实际结果与预期结果进行比较(断言),从而判定用例是否执行通过。单元测试框架一般会提供丰富的断言方法。例如:判断相等/不等、包含/不包含、True/False的断言方法等。
  • 提供丰富的日志:当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后提供丰富的执行结果。例如:,总执行时间、失败用例数、成功用例数等。
unnittest介绍

什么是单元测试?

单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。

compute.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/2 15:27'


class Count:
    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    def calculate(self):
        return self.a + self.b

test.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 11:56'

import unittest  # 导入unittest框架
from compute import Count # 导入用例集


class TestCount(unittest.TestCase):
    def setUp(self):
     """用例初始化"""
print("test start") def test_add(self): """执行用例1""" print(Count(2, 3)) self.assertEqual(Count(2, 3).calculate(), 5) def tearDown(self):
     """用例执行完毕,收尾"""
print("test end ") if __name__ == '__main__': unittest.main() # 固定调用方法

用例执行流程:

  • setUp初始化方法第一个执行,处理一些初始化操作。
  • 接着runTest执行用例,用例返回True。
  • 最后,tearDown打扫战场!

在每个用例执行时,setUp和tearDown都会执行。

注意:

  • myUnitTest类名可以自定义,但是必须继承unittest.TestCase
  • 示例中的setUp和tearDown方法名是固定的,但测试用例,没有初始化和收尾的工作,setUp和tearDown方法可以省略不写。
  • 测试用例必须已test开头,unittest.main()就可以认识然后直接运行。
unittest的断言

unittest.TestCase提供了一些断言方法用来检查并报告故障

最常用的方法:

 示例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 12:09'

import unittest


class TestStringMethods(unittest.TestCase):
    def test_assertEqual(self):
        self.assertEqual(1, 2, msg='1 != 2')  # AssertionError: 1 != 2 : 1 != 2

    def test_assertTrue(self):
        self.assertTrue('')

    def test_assertFalse(self):
        self.assertFalse('')


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

所有的assert方法都接收一个msg参数,如果指定,该参数将用作失败时的错误提示。

结果:

F.F
======================================================================
FAIL: test_assertEqual (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "s7.py", line 11, in test_assertEqual
    self.assertEqual(1, 2, msg='1 != 2')  # AssertionError: 1 != 2 : 1 != 2
AssertionError: 1 != 2 : 1 != 2

======================================================================
FAIL: test_assertTrue (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "s7.py", line 14, in test_assertTrue
    self.assertTrue('')
AssertionError: '' is not true

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=2)

F.F表示,如果用例通过返回.,失败返回F,结果告诉3个用例成功1个,失败2个。

如何在用例中输出用例名和用例描述信息

self._testMethodName  用例名
self._testMethodDoc     用例的描述信息,即方法的注释内容
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 12:09'

import unittest


class TestStringMethods(unittest.TestCase):
    def test_assertEqual(self):
        """描述信息"""
        print(self._testMethodName, self._testMethodDoc)
        self.assertEqual(1, 1, msg='1 != 2')  # AssertionError: 1 != 2 : 1 != 2


if __name__ == '__main__':
    unittest.main()
unittestTestSute

TestSuite是测试套件,承载多个用例的集合,该集合中有多个用例,可以理解为一个盒子,该盒子中存放多个用例

当所有用例都添加到了盒子中,然后找一个执行器,去执行盒子中的测试用例。

流程:

实例化所有的用例

  • 创建一个盒子
  • 将用例添加到盒子中
  • 将所有用例都收集到盒子中后,使用执行器执行盒子中的测试用例

使用addTests

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 12:09'

import unittest


class MyCase(unittest.TestCase):

    def test_is_upper(self):
        self.assertTrue("Foo".isupper())

    def test_is_lower(self):
        self.assertTrue("foo".islower())


if __name__ == '__main__':
    # 实例化用例
    case_01 = MyCase(methodName="test_is_upper")
    case_02 = MyCase(methodName="test_is_lower")
    # 创建suite容器
    suite = unittest.TestSuite()
    # 将用例添加到盒子中
    # 方式1:每个用例单独添加
    # suite.addTest(case_01)
    # suite.addTest(case_02)
    # 方式2:用例对象已列表类型添加
    suite.addTests([case_01, case_02])
    # 使用执行器执行盒子suite中的用例
    runner = unittest.TextTestRunner()
    runner.run(suite)

使用Map

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 12:09'

import unittest


class MyCase(unittest.TestCase):

    def test_is_upper(self):
        self.assertTrue("Foo".isupper())

    def test_is_lower(self):
        self.assertTrue("foo".islower())


if __name__ == '__main__':
    # 实例化用例
    case_obj = map(MyCase, ["test_is_upper", "test_is_lower"])
    print(case_obj, list(case_obj))
    # 创建suite容器
    suite = unittest.TestSuite()
    # 将用例添加到盒子中
    suite.addTests(case_obj)
    # 返回suite中测试用例个数
    print(suite.countTestCases())
    # 使用执行器执行盒子suite中的用例
    runner = unittest.TextTestRunner()
    runner.run(suite)

unittest.TestSuite中方法:

- addTest   一个一个添加

- addTests 批量添加

- suite.countTestCases() 用例个数

unittest.makeSuite

unittest.makeSuite实例化suite时,同时进行测试用例收集,返回收集完成的suite,最后交给执行器去执行。

unittest其它方法

发现其它目录中脚本用例:

unittest.TestLoader().loadTestsFromModule()找到到指定模块下面的TestCase子类,获取其中以test开头的用例。

suite = unittest.TestLoader().loadTestsFromModule(test_case) # 执行test_case.py文件中的用例,如果在其它目录需要from导入
# 执行器执行
unittest.TextTestRunner(verbosity=2).run(suite)
(单个用例)执行指定模块名、类名、用例名进行执行
    from scripts import  ff_case
    suit = unittest.TestLoader().loadTestsFromName(
        name="MyTestCase.test_case_01", # 类.用例名称
        module=ff_case  # 模块名
    )
    unittest.TextTestRunner(
        verbosity=2
    ).run(suit)

(多个用例)执行指定模块名、类名、多条用例名进行执行

  from scripts import ff_case
    suit = unittest.TestLoader().loadTestsFromNames(
        names=[  # 列表形式指定多条用例
            "MyTestCase.test_case_01",  # ff_case.py -->MyTestCae类-->test_case_01用例
            "MyTestCae.test_case_02"  # ff_case.py -->MyTestCae类-->test_case_02用例
        ],
        module=ff_case  # 指定模块名
    )
    unittest.TextTestRunner(
        verbosity=2
    ).run(suit)
unittest.TestLoader().discover(),找到指定目录中,指定测试用例
注意:discover只会收集Python包中以pattern开头的脚本,再找脚本中unittest.TestCase子类中以test开头的测试用例
# 发现指定目录中所有合法脚本中合法的测试用例
unittest.TestLoader().discover(
        top_level_dir="", # 顶级目录
        start_dir="", #指定目录
        pattern="*_test.py",# 匹配文件模式
    )
setUpClass 和 tearDownClass

测试某一个用例时,都会对应的执行三个方法:

  • setUp,开头一枪的那家伙,它负责该用例之前可能需要的一些准备,比如连接数据库。
  • runTest,执行用例逻辑,没的说,干活的长工。
  • tearDown,负责打扫战场,比如关闭数据库连接。

 compute.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 12:09'

import unittest
import compute


class myUnitTest(unittest.TestCase):
    def test_add(self):
        self.assertEqual(compute.add(2, 3), 5)

    def test_sub(self):
        self.assertEqual(compute.sub(10, 5), 5)

    def setUp(self):
        """测试用例之前执行,无论我在myUnitTest的什么位置"""
        print("打开数据库")

    def tearDown(self):
        """测试用例之后执行,无论我在myUnitTest的什么位置"""
        print("关闭数据库")


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

结果:

打开数据库                                                                                                                                                                                 
关闭数据库                                                                                                                                                                                 
.打开数据库                                                                                                                                                                                
关闭数据库                                                                                                                                                                                 
.                                                                                                                                                                                     
----------------------------------------------------------------------                                                                                                                
Ran 2 tests in 0.001s                                                                                                                                                                 
                                                                                                                                                                                      
OK 

两个用例被执行并通过,并且每一个用例执行前后都触发了setUp和tearDown方法执行。如果这是由1000个甚至更多的用例组成的用例集,并且每一个用例都去操作数据,那么每个用例都会做连接/关闭数据库的操作。这就有问题了,就不能一次连接,所有用例完事后,再关闭。

解决方案:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 12:09'

import unittest
import compute


class myUnitTest(unittest.TestCase):
    def test_add(self):
        self.assertEqual(compute.add(2, 3), 5)

    def test_sub(self):
        self.assertEqual(compute.sub(10, 5), 5)

    @classmethod
    def setUpClass(cls):
        """测试用例之前执行,无论我在myUnitTest的什么位置"""
        print("在用例集开始执行,我去建立数据库连接...")

    @classmethod
    def tearDownClass(cls):
        """测试用例之后执行,无论我在myUnitTest的什么位置"""
        print("全军撤退,关闭数据库")


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

结果:

在用例集开始执行,我去建立数据库连接...                                                                                                                                               
test_add (__main__.myUnitTest) ... ok
test_sub (__main__.myUnitTest) ... ok
全军撤退,关闭数据库                                                                                                                                                                  

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

由结果可以看到,setUpClass和tearDownClass这两个类方法完美的解决了问题,让我们在某些情况下可用更加灵活的组织逻辑。

verbosity参数

verbosity控制错误输出的详细程度:

在执行unittest.main(verbosity=1)时,可以通过verbosity参数来控制错误信息的详细程度。

verbosity=0

在用例集开始执行,我去建立数据库连接...                                                                                                                                               
全军撤退,关闭数据库                                                                                                                                                                  
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

verbosity=1

在用例集开始执行,我去建立数据库连接...                                                                                                                                               
..全军撤退,关闭数据库                                                                                                                                                                

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

verbosity=2

在用例集开始执行,我去建立数据库连接...                                                                                                                                               
test_add (__main__.myUnitTest) ... ok
test_sub (__main__.myUnitTest) ... ok
全军撤退,关闭数据库                                                                                                                                                                  

----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK

由结果总结,verbosity有3种错误信息状态提示:

  • 0,静默模式,对于测试结果给予简单提示。
  • 1,默认模式,与静默模式类似,只是在每个成功的用例前面有个.每个失败的用例前面有个F,跳过的用例有个S
  • 2,详细模式,测试结果会显示每个用例的所有相关的信息。

切记,只有0、1、2三种状态。
默认的是1。

-v

除此之外,我们在终端执行时也可以输出详细报告:

E:\testselenium>python s7.py -v
在用例集开始执行,我去建立数据库连接...                                                                                                                                            
test_add (__main__.myUnitTest) ... ok
test_sub (__main__.myUnitTest) ... ok
全军撤退,关闭数据库                                                                                                                                                                  

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

如上示例,使verbosity参数保持默认,我们通过在终端加-v来输入详细报告信息。

skip跳过测试用例

unittest支持跳过单个测试方法甚至整个测试类。

我们可以使用unittest提供的相关装饰器来完成:

 示例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2021/3/3 14:32'

import unittest


class TestCase01(unittest.TestCase):
    def test_assertTrue(self):
        self.assertTrue(" ")

    @unittest.skip("跳过该条用例")
    def test_assertFalse(self):
        self.assertFalse(" ")


@unittest.skip("跳过这个用例类")
class TestCase02(unittest.TestCase):
    def test_assertTrue(self):
        self.assertTrue("")

    def test_assertFalse(self):
        self.assertFalse("")


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

结果:

E:\testselenium>python s8.py -v
test_assertFalse (__main__.TestCase01) ... skipped '跳过该条用例'                                                                                                                           
test_assertTrue (__main__.TestCase01) ... ok                                                                                                                                          
test_assertFalse (__main__.TestCase02) ... skipped '跳过这个用例类'                                                                                                                          
test_assertTrue (__main__.TestCase02) ... skipped '跳过这个用例类'                                                                                                                           
                                                                                                                                                                                      
----------------------------------------------------------------------                                                                                                                
Ran 4 tests in 0.001s                                                                                                                                                                 
                                                                                                                                                                                      
OK (skipped=3)   

共4个用例,一个用例类被跳过,另一个用例类中跳过一个方法,那么就是执行4个用例,跳过3个。

用例结果输出文件中

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 青城子
# datetime: 2022/2/10 14:44 
# ide: PyCharm

import unittest


class MyCase(unittest.TestCase):

    def test_case_01(self):
        self.assertTrue(1)

    # @unittest.skip(reason="无条件跳过")
    def test_case_02(self):
        self.assertTrue("")

    # @unittest.skipIf(condition=3 < 2, reason="有条件跳过")
    def test_case_03(self):
        self.assertTrue(0)


if __name__ == '__main__':
    suite = unittest.makeSuite(testCaseClass=MyCase, prefix="test")
    print(suite)
    # unittest.TextTestRunner().run(suite)  # 执行屏幕输出
    # 将屏幕输出转换成文本输出
    with open("a.txt", "w") as f:
        unittest.TextTestRunner(stream=f).run(suite)

主要使用stream参数,给一个文件句柄。

posted on 2021-03-03 15:00  赛兔子  阅读(136)  评论(0编辑  收藏  举报

导航