1. UnitTest - Test fixture

1.1 前言

前面已经涉及到了test case, test suite, test runner这三个概念了,还有test fixture没有提到,那什么是test fixture呢?在TestCase的docstring中有这样一段话:

Test authors should subclass TestCase for their own tests. Construction and deconstruction of the test’s environment (‘fixture’) can be implemented by overriding the ‘setUp’ and ‘tearDown’ methods respectively.

可见,对一个测试用例环境的搭建和销毁,是一个fixture,通过覆写TestCase的setUp()和tearDown()方法来实现。这个有什么用呢?比如说在这个测试用例中需要访问数据库,那么可以在setUp()中建立数据库连接以及进行一些初始化,在tearDown()中清除在数据库中产生的数据,然后关闭连接。注意tearDown的过程很重要,要为以后的TestCase留下一个干净的环境。

1.2 前置fixture和后置 fixture的使用

  1. 根据代码的层次结构,当前的前后置fixture共有三个层次的应用,对应得方法为:
  • 模块级别-setUpModule/tearDownModule

          setUpModule :整个模块开始前执行<br />           tearDownModule:整个模块结束时执行 <br />   
    
  • 类级别-setUpClass/tearDownClass

        setUpClass:测试用例类开始前执行<br />           tearDownClass:测试用例类结束时执行   <br />    
    
  • 用例级别-setUp/tearDown

       setUp : 测试用例开始前执行(以一条测试用例为单位)<br />          tearDown :测试用例结束时执行(以一条测试用例为单位)<br />**   **<br />**    实例 5-1 :**
    

目录结构
image.png

代码:

  • under_test 包 math模块

这里定义了加减乘除4个被测函数

# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 20:00

def add(a,b):
    return a+b

def sub(a,b):
    return a-b

def mult(a,b):
    return a*b

def div(a,b):
    return a/b
  • test_case 包

     <br />          Test_A_B 模块  
    

    ```python

    -- coding:utf-8 --

    Author: tang_ren_li

    2021-1-6 22:45

import unittest

def setUpModule():#测试类初始化工作 每个测试模块运行前执行一次 print(“Test_A_B 模块 模块级初始化完成“)

整个模块结束时执行

def tearDownModule():#测试类收尾工作 每个测试模块运行完毕后执行一次 print(“Test_A_B 模块 模块级收尾工作完成“)

class Test_A(unittest.TestCase):

   @classmethod
   def setUpClass(cls):  # 测试类初始化工作 每个测试类运行前一次
    print("*****测试A类的初始化工作完成 !本测试类开始测试*****")

   def test_caseA1(self):
        print("test_case A1 is run !")
        pass

   def test_caseA2(self):
       print("test_case A2 is run !")
       pass

   def test_caseA3(self):
       print("test_case A3 is run !")
       pass

   @classmethod
   def tearDownClass(cls):  # 测试类收尾工作 每个测试类运行完后执行一次
       print("*****测试A类的收尾工作完成! 本测试类测试结束*****")

class Test_B(unittest.TestCase):

   def test_caseB1(self):
       print("test_case B1 is run !")
       pass

   def test_caseB2(self):
       print("test_case B2 is run !")
       pass

   def test_caseB3(self):
       print("test_case B3 is run !")
       pass
 <br />  Test_math 模块  
```python
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 22:45

import unittest
from example_5_1.under_test import math

def setUpModule():#测试类初始化工作 每个测试模块运行前执行一次
    print("***Test math模块 模块级初始化完成***")


class Test_Math(unittest.TestCase):

      @classmethod
      def setUpClass(cls): #测试类初始化工作 每个测试类运行前一次
          print("*****测试数学类的初始化工作完成 !本测试类开始测试*****")

      def setUp(self):#测试用例初始化工作 每个测试用例运行前执行一次
          print("*****测试数学类里测试用例初始化工作完成!本测试用例开始测试*****")

      def test_add(self):
          print("加法测试用例运行!")
          self.assertEqual(math.add(5,5),10)

      def test_sub(self):
          print("减法测试用例运行!")
          self.assertEqual(math.sub(5,5),0)

      def test_mult(self):
          print("乘法测试用例运行!")
          self.assertEqual(math.mult(5, 5), 25)

      def test_div(self):
          print("除法测试用例运行!")
          self.assertEqual(math.div(5, 5), 1)


      @classmethod
      def tearDownClass(cls):  # 测试类收尾工作 每个测试类运行完后执行一次
          print("*****测试数学类的收尾工作完成! 本测试类测试结束*****")

      def tearDown(self):#测试用例收尾工作 每个测试用例运行完后执行一次
          print("*****测试数学类的收尾工作完成! 本测试用例测试结束*****")


def tearDownModule():#测试类收尾工作 每个测试模块运行完毕后执行一次
    print("***Test math模块 模块级收尾工作完成***")

run 包:

case_make_suit 模块

# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 22:45

import unittest

import sys
sys.path.append(r'D:\test\example_5_1\test_case')

def  test_suit():

    suit=unittest.TestSuite()

    testcase=unittest.TestLoader().discover(r'D:\test\example_5_1\test_case')

    suit.addTest(testcase)

    return suit

run 模块

# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-18 22:45

import unittest

from example_5_1.run import case_make_suit

if __name__ == '__main__':

    runner = unittest.TextTestRunner(verbosity=2)

    runner.run(case_make_suit.test_suit())


执行结果:

Test_A_B 模块 模块级初始化完成

*测试A类的初始化工作完成 !本测试类开始测试*
test_case A1 is run !
test_case A2 is run !
test_case A3 is run !
*测试A类的收尾工作完成! 本测试类测试结束*

test_case B1 is run !
test_case B2 is run !
test_case B3 is run !

Test_A_B 模块 模块级收尾工作完成

Test math模块 模块级初始化完成
*测试数学类的初始化工作完成 !本测试类开始测试*

*测试数学类里测试用例初始化工作完成!本测试用例开始测试*
加法测试用例运行!
*测试数学类的收尾工作完成! 本测试用例测试结束*

*测试数学类里测试用例初始化工作完成!本测试用例开始测试*
除法测试用例运行!
*测试数学类的收尾工作完成! 本测试用例测试结束*

*测试数学类里测试用例初始化工作完成!本测试用例开始测试*
乘法测试用例运行!
*测试数学类的收尾工作完成! 本测试用例测试结束*

*测试数学类里测试用例初始化工作完成!本测试用例开始测试*
减法测试用例运行!
*测试数学类的收尾工作完成! 本测试用例测试结束*

*测试数学类的收尾工作完成! 本测试类测试结束*
Test math模块 模块级收尾工作完成

test_caseA1 (Test_A_B.Test_A) … ok
test_caseA2 (Test_A_B.Test_A) … ok
test_caseA3 (Test_A_B.Test_A) … ok
test_caseB1 (Test_A_B.Test_B) … ok
test_caseB2 (Test_A_B.Test_B) … ok
test_caseB3 (Test_A_B.Test_B) … ok
test_add (Test_math.Test_Math) … ok
test_div (Test_math.Test_Math) … ok
test_mult (Test_math.Test_Math) … ok
test_sub (Test_math.Test_Math) … ok

———————————————————————————————————
Ran 10 tests in 0.001s
OK

说明:

  1. 类级别-setUpClass/tearDownClass 方法是 unittest.testcase 的类方法
    所以要加类方法的装修器,参数标识为cls

    2 . fixture 初始化执行顺序时 从上往下, 从外到里, 而收尾恰好相反 。 是不是有点像C++的构造函数和
    析构函数!

    顺序为:

    1. 模块级别-初始化
      2. 类级别-初始化
      3. 测试用例级别-初始化
      4. 执行测试用例
      5. 测试用例级-收尾
      6. 类级别-收尾
      7. 模块级别-收尾

      1.3 测试用例跳过,标记预计失败的测试用例

1.3.1 指定跳过某些测试用例,有条件的跳过测试用例

                unittest 以下装饰器和异常实现了测试方法及测试类的跳过:

@unittest.skip(原因
无条件跳过装饰测试。 原因应说明为何跳过测试。

@unittest.skipIf(条件原因
如果条件为真,则跳过修饰的测试。

@unittest.skipUnless(条件原因
除非条件为真,否则跳过装饰性测试。

例子 5-2:
** 代码:

# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-20 20:40

import unittest
import sys

def conditon_judge1():

      #判断当前运行代码的操作系统
      judgement=(sys.platform.startswith("win32"))

      return  judgement


def conditon_judge2():

    judgement = (sys.platform.startswith("Linux"))

    return judgement


class Test_sikp_testcase(unittest.TestCase):

      def test_A(self):
          print("测试用例A 执行")
          self.assertEqual(1,1)

      @unittest.skip  #无条件直接跳过
      def test_B(self):
          print("测试用例B 执行")
          print("我被跳过了,所以你看不到我执行")
          self.assertEqual(2,2)

      # conditon_judge()1 为Ture 跳过
      @unittest.skipIf(conditon_judge1(), '系统为windows 跳过不执行')
      def test_C(self):
          print("测试用例C 执行")
          self.assertEqual(2,2)

      # conditon_judge()2 为False 跳过
      @unittest.skipUnless(conditon_judge2(),'系统不是Linux,跳过不执行,除非系统Linux则执行')
      def test_D(self):
          print("测试用例D执行")
          self.assertEqual(2,2)

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

运行结果:

collecting … collected 4 items

Skip_test.py::Test_sikp_testcase::test_A PASSED [ 25%]测试用例A 执行

Skip_test.py::Test_sikp_testcase::test_B SKIPPED [ 50%]
Skipped

Skip_test.py::Test_sikp_testcase::test_C SKIPPED (系统为windows 跳过…) [ 75%]
Skipped: 系统为windows 跳过不执行

Skip_test.py::Test_sikp_testcase::test_D SKIPPED (系统不是Linux,跳…) [100%]
Skipped: 系统不是Linux,跳过不执行,除非系统Linux 则执行

=========== 1 passed, 3 skipped in 0.02s =========================

进程已结束,退出代码0

说明:

  1. 通过条件判断, 比如软件版本, 配置文件等 ,也可以设置开关变量,决定哪一批测试用例执行或不执行

1.3.2 标记预计失败的测试用例

把测试标注成“预期失败”的测试。这些坏测试会失败,但不会算进 TestResult 的失败里

对于一些已知有问题的测试脚本, 或者此测试用例已知是fail, 被产品功能未完善等可以采取这种
方式处理

@unittest.expectedFailure
Mark the test as an expected failure or error. If the test fails or errors in the test function itself (rather than in one of the test fixture methods) then it will be considered a success. If the test passes, it will be considered a failure.

代码:

# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-20 20:40


import unittest
import sys

def conditon_judge1():

      judgement=(sys.platform.startswith("win32"))

      return  judgement


def conditon_judge2():

    judgement = (sys.platform.startswith("Linux"))

    return judgement


class Test_sikp_testcase(unittest.TestCase):


      def test_A(self):
          print("测试用例A 执行")
          self.assertEqual(1,1)

      @unittest.skip("我想跳过此测试用例")  #无条件直接跳过
      def test_B(self):
          print("测试用例B 执行")
          print("我被跳过了,所以你看不到我执行")
          self.assertEqual(2,2)

      # conditon_judge()1 为Ture 跳过
      @unittest.skipIf(conditon_judge1(), '系统为windows 跳过不执行')
      def test_C(self):
          print("测试用例C 执行")
          self.assertEqual(2,2)

      # conditon_judge()2 为False 跳过
      @unittest.skipUnless(conditon_judge2(),'系统不是Linux,跳过不执行,除非系统Linux则执行')
      def test_D(self):
          print("测试用例D执行")
          self.assertEqual(2,2)


     #预先标记为Fail  显示结果标识为xFail
      @unittest.expectedFailure
      def test_E(self):
          print("测试用例D执行 xFail 不算入统计结果")
          self.assertEqual(3, 2)

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

运行结果

=================== 1 passed, 3 skipped, 1 xfailed in 0.10s ===================
进程已结束,退出代码0
PASSED [ 20%]测试用例A 执行
SKIPPED (我想跳过此测试用例) [ 40%]
Skipped: 我想跳过此测试用例
SKIPPED (系统为windows 跳过…) [ 60%]
Skipped: 系统为windows 跳过不执行
SKIPPED (系统不是Linux,跳…) [ 80%]
Skipped: 系统不是Linux,跳过不执行,除非系统Linux则执行
XFAIL [100%]测试用例D执行 xFail 不算入统计结果

2. UnitTest - 测试报告的生成

     这里主要介绍unittest 生成报告的2个模块类, 一个是Unittest自带的模块类TextRunner ,一个是HtmltestRunner是一个第三方的插件模块

2.1 Unittest之TextTestRunner

     **利用TestTestRunner  可以将报告输出到控制台,或者txt 文件**

    TextTestRunner 有很多参数,一般情况下,我们只需要理解其中三种:<br />        TextTestRunner(**stream**=None, descriptions=None, verbosity=0)

       **stream**: 是测试报告数据信息流,类型是一个文件流对象,可以通过文件操作函数获得,默<br />                          认值None, 此时测试数据将输出到控制台。      

       description: 测试报告描述

      verbosity:  测试报告数据的详细程度,和前面介绍的unittest.mian() 中是一个意思

       stream一般指向 open()

       python open() 函数用于打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写    <br />           例如:    

       stream_f=open('文件路径', 'w', '编码方式')    w 表示写入

     <br />         ** 代码:**<br />         <br />          基于例子5-1 中 run 模块进行修改<br />           
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-24 22:45

import unittest

from example_5_1.run import case_make_suit

if __name__ == '__main__':
 #打开文件
 with open(file=r'D:\test\example_5_1\report\report.txt',mode='w',encoding="utf-8") as f_report:

      #输出报告数据流到文件
      runner = unittest.TextTestRunner(stream=f_report, descriptions='test_report', verbosity=2)

      runner.run(case_make_suit.test_suit())
 #关闭文件 
 f_report.close()

运行结果:

将测试报告数据流输出到路径文件: D:\test\example_5_1\report\report.txt

image.png
说明:

  1. 这种程度的报告很明显是无法满足我们实际应用的需求的

2.2 Unittest之HTMLtestRunner

HTMLtestRunner 是一个基于参考TextTestRunner 第三方开发的报告模块

可以生成一个漂亮的HTML报告,可以满足工作中的实际应用需求。

2.2.1 模块安装

          在pip库里可以提供英文版和中文版模块下载:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1643026452729-3e0c9d14-d2f5-4555-8242-081a1f497faf.png#clientId=ua68e4278-0b08-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=170&id=u597802ad&margin=%5Bobject%20Object%5D&name=image.png&originHeight=340&originWidth=1287&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39404&status=done&style=none&taskId=uee21aaeb-fb12-407d-9376-ff444811754&title=&width=643.5)

      通过命令  ** pip install haohan-HTMLTestRunner  安装汉化版  **

      安装完毕可以通过:**pip show **来查看是否安装成功和当前版本<br />        ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1643026608445-512439ad-be37-43de-8dc4-b7c7ab531d3b.png#clientId=ua68e4278-0b08-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=116&id=ue30012f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=232&originWidth=671&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22913&status=done&style=none&taskId=uc50859d9-1d2c-4a0b-9d8f-576cf9a81c2&title=&width=335.5)

2.2.2 模块使用

          首先需要 import 模块  **haohan-HTMLTestRunner**<br />               <br />               然后需要使用  HTMLTestRunner(stream, tester, titledescription ,verbosity)  <br />       <br />               参数含义和TextTestRunner几乎相同, 多了个tester 表示测试人员描述


          **代码:**<br />         <br />              基于例子5-1 中 run 模块进行修改<br />             
# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-24 22:45

import unittest
import time
import os
from example_5_1.run import case_make_suit
import haohan_HTMLTestRunner

if __name__ == '__main__':

    # 1、获取当前时间,并格式化字符,这样便于下面的使用。
    now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))

    # 2、html报告文件路径生成(绝对路径+报告文件)
    report_abspath = os.path.join(r'D:\test\example_5_1\report', "result_" + now + ".html")

    # 3、打开一个文件,将result写入此file中
    fp = open(report_abspath, "wb")

    runner=haohan_HTMLTestRunner.HTMLTestRunner(stream=fp,tester='tangren_li',title="web UI 测试报告",description="测试用例执行情况: ",verbosity=2)

    runner.run(case_make_suit.test_suit())

    fp.close()
         <br />    ** 报告生成:**<br />  ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1643030698771-1632b879-199b-4042-84a9-0f39da542446.png#clientId=ua68e4278-0b08-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=155&id=ue53ea392&margin=%5Bobject%20Object%5D&name=image.png&originHeight=310&originWidth=383&originalType=binary&ratio=1&rotation=0&showTitle=false&size=28219&status=done&style=none&taskId=u1f0d1aad-9b7f-492e-98db-e80b818840d&title=&width=191.5)<br />      根据当前时间戳不覆盖生成报告


报告样式:

image.png

说明

  1. 用法和TextTestRunner几乎相同 , 但文件写入模式需要用 ‘wb’ 二进制字符写入
    2. 以当时时间戳生成报告名的方法在工作中非常的实用
    3. 由于HTML 报告需要加载CSS 所以必须第一加载需要在联网情况下才能显示完整样
    式,但只需要加载一次就可以。

后记

关于Unittest 测试框架的介绍到目前就告一段落了, 当然目前介绍到知识只能算冰山一角, 但也算是五脏俱全了,后面如果有接触到有用的新的知识应用这边在持续更新。