python-UnitTest框架

427 阅读11分钟

一、UnitTest基本介绍

1、是什么?

UnitTest是python自带的专门用于单元测试的。一般单元测试是开发做的。(框架:解决一类事情的功能集合)

自带的框架:不需要额外安装,只要安装了python就可以使用
第三方框架:想要使用,需要先安装后使用(pytest)

对于测试来说,使用unittest框架来管理运行多个测试用例的。

2、作用

  • 能够组织多个用例去执行
  • 提供丰富的断言方法(让程序代码代替人工自动判断预期结果和实际结果是否相符合
  • 能够生成测试报告

3、核心要素(组成)

  • TestCase(最核心的模块)

    TestCase(测试用例),注意这个测试用例是unittest框架的组成部分,不是手工和自动化中我们所说的用例(Test  Case)
    主要作用: 每个 TestCase(测试用例)都是一个代码文件,在这个代码文件中,来书写真正的用例代码
    
  • TestSuite(测试套件)

    TestSuite(测试套件),用来管理 组装(打包)多个TestCase(测试用例)的。也是一个代码文件
    
  • TestRunner(测试执行)

    TestRunner(测试执行、测试运行),用来执行TestSuite(测试套件)的
    
  • TestLoader(测试加载)

    TestLoader(测试加载),功能是对TestSuite(测试套件)功能的补充
    
  • Fixture(测试夹具)

    Fixture(测试夹具),书写在TestCase(测试用例)代码中,是一种代码结构,在每个方法执行前后都会执行的内容
    例如:登录的测试用例(把相同且重复的内容放到TestCase中,只写一遍,但每次用例方法的执行,都会自动执行Fixture中的代码)
      	1.打开浏览器
      	2.输入网址
    

二、TestCase(测试用例)

  1. 是一个代码文件,在代码文件中,用来书写真正的用例代码。
  2. 代码文件的名字必须按照标识符的规则来书写,可以将代码的作用在文件的开头使用注释说明
  3. 步骤:
  • ① 导包(unittest) ——import unittest

  • ② 自定义测试类

     class 类名(unittest.TestCase):
              pass
              
    
  • ③ 在测试类中书写测试方法

      def  test_方法名(self):
             用例的代码
    
  • ④ 执行用例

示例

'''
代码的目的:学习TestCase(测试用例)模块的书写方法
'''
# 1. 导包
import unittest
# 2.自定义测试类,需要继承unittest模块中的TestCase类
class TestDemo(unittest.TestCase):
        # 3.书写测试方法,即 用例代码,目前没有真正的用例代码,使用print代替
        # 书写要求,测试方法必须以test_ 开头(本质是以test开头)
        def test_method1(self):
            print("测试方法1")

        def test_method2(self):
            print("测试方法2")
# 4.执行用例(方法)
# 4.1 将光标放到类名后面,会执行类里所有方法
# 4.2 放到方法名后面,只执行当前的方法```
  1. 代码书写常见错误:
  • 代码文件命名不规范
  • 代码运行没结果(右键运行没有unittests for 的提示)
  • 没有找到用例(测试方法中不是以test_开头的,或者单词写错了)

三、TestSuite和TestRunner的书写

TestSuite管理、打包、组装TestCase文件

TestRunner执行TestSuite (套件)
步骤:

  • ① 导包(unittest)

  • 实例化(创建对象)套件对象

    suite = unittest.TestSuite()
    
  • ③ 使用套件对象添加用例方法

     suite.addTest(unittest.makeSuite(测试类名))或
     suite.addTest(测试类名('方法名'))
    
  • ④ 实例化运行对象

      runner= unittest.TextTestRunner()
    
  • ⑤ 使用运行对象去执行套件对象

      runner.run(suite)	            
    

示例

'''
学习TestSuite和TestRunner的使用
'''
# 1.导包(unittest)
import unittest
# 2.实例化(创建对象)套件对象
from testCase1 import TestDemo1
from testCase2 import TestDemo2

suite = unittest.TestSuite()
# 3.使用套件对象添加用例方法
#方式一,套件对象.addTest(测试类名(‘方法名’))#建议测试类名和方法名直接去复制,不要手写
suite.addTest(TestDemo1('test_method1'))
suite.addTest(TestDemo1('test_method2'))
suite.addTest(TestDemo2('test_method1'))
suite.addTest(TestDemo2('test_method2'))
#方式二,将一个测试类中的所有方法进行添加,
#套件对象.addTest(unittest.makeSuite(测试类名))
#缺点:makeSuite()不会提示
suite.addTest(unittest.makeSuite(TestDemo1))
suite.addTest(unittest.makeSuite(TestDemo2))

# 4.实例化运行对象
runner = unittest.TextTestRunner()
# 5.使用运行对象去执行套件对象
#运行对象.run(套件对象)
runner.run(suite)

运行结果注意:

image.png

练习

image.png

#test.py
#1.导包
import unittest
from tools import add
#2. 自定义测试类
class TestAdd(unittest.TestCase):
    # 3. 书写测试方法,就是测试用例
    def test_method1(self):
        #1,2,3
        if add(1,2) == 3:
            print("测试通过")
        else:
            print("测试不通过")
    def test_method2(self):
        if add(10, 20) == 20:
            print("测试通过")
        else:
            print("测试不通过")
#案例练习
#1.导包
import unittest
# 2.实例化(创建对象)套件对象
from test import TestAdd

suite=unittest.TestSuite()
# 3.使用套件对象添加用例方法
suite.addTest(unittest.makeSuite(TestAdd))
# 4.实例化运行对象
runner = unittest.TextTestRunner()
# 5.使用运行对象去执行套件对象
runner.run(suite)

四、TestLoader(测试加载)

作用和TestSuite的作用一样,对TestSuite功能的补充,用来组装测试用例的。比如:如果TestCase的代码文件有很多。

步骤

  • 1.导包

  • 2.实例化测试加载对象并添加用例——>得到的是suite对象

      suite = unittest.TestLoader().discover('用例所在的路径',‘用例的代码文件名’)
    
  • 3.实例化 运行对象

  • 4.运行对象执行套件对象

#1.导包
import unittest

#2.实例化加载对象并添加用例
#第一种: unittest.TestLoader().discover('用例所在的路径',‘用例的代码文件名’)
# 用例所在的路径,建议使用相对路径,用例的代码文件名可以使用* (任意多个任意字符)通配符
# suite=unittest.TestLoader().discover('./case','test*.py')
suite=unittest.TestLoader().discover('./case','*case*.py')

#第二种:使用默认的加载对象并加载用例,防止忘记加TestLoader()的括号
suite=unittest.defaultTestLoader.discover('case','*case*')

#3. 实例化运行对象
runner= unittest.TextTestRunner()
#4.执行
runner.run(suite)
#将3,4步合并
# unittest.TextTestRunner().run(suite)

五、Fixture(测试夹具)

是一种代码结构,在某些特定的情况下,会自动执行。 (方法级别和类级别前后的方法,不需要同时出现,根据用例代码的需要自行的选择使用)

1. 方法级别(掌握)

每个测试方法(用例代码)执行前后都会自动调用结构。

#方法执行之前
def setUp(self):
       每个测试方法执行之前都会执行
       pass
#方法执行之后
def  tearDown(self):
       每个测试方法执行之前都会执行
      pass

2.类级别(掌握)

每个测试类中所有方法执行前后都会自动调用结构,在整个类中执行之前执行之后使用一次。

#类级别的Fixture方法,是一个类方法
#类中所有方法之前
@classmethode
def  setUpClass(cls):
        pass

#类中所有方法之后
@classmethod
def   tearDownClass(cls):
         pass

3.模块级别

每个模块(代码文件)执行前后执行的代码结构。

#模块级别的需要写在类的外边直接定义函数即可
#代码文件之前
def  setUpModule():
         pass
#代码文件之后
def tearDownModule():
        pass

案例:

  • 1.打开浏览器(整个测试过程中就打开一次浏览器)——>类级别
  • 2.输入网址(每个测试方法都要一次)——>方法级别
  • 3.输入用户名、密码和验证码点击登录(不同的测试数据)——>测试方法
  • 4.关闭当前页面(每个测试方法都需要一次) ——>方法级别
  • 5.关闭浏览器(整个测试过程中就关闭一次浏览器)——>类级别
import unittest
def login(username,password):
    if username == 'admin' and password == '123456':
        return '登录成功'
    else:
        return '登录失败'
class TestLogin(unittest.TestCase):
    def setUp(self) -> None:
        print('输入网址')
    def tearDown(self) -> None:
        print('关闭当前页面')

    @classmethod
    def setUpClass(cls) -> None:
        print('1.打开浏览器')
    @classmethod
    def tearDownClass(cls) -> None:
        print('5.关闭浏览器')
    def test_username_password_ok(self):
        self.assertEqual(login('admin','123456') ,'登录成功')

        if login('admin','123456') == '登录成功':
            print('pass')
        else:
            print('fail')
    def test_username_error(self):
        self.assertEqual(login('root', '123456'), '登录失败')
        self.assertIn('失败',login('root', '123456'))

        if login('root', '123456') == '登录失败':
            print('pass')
        else:
            print('fail')

六、断言

让程序代替人为判读测试程序执行结果是否符合预期结果的过程

优点

  • 1.提高测试效率
  • 2.实现自动化测试(让脚本在无人值守的状态下运行)

结果

  • true,用例通过
  • false,代码抛出异常,用例不通过

常用的UnitTest断言方法

image.png

image.png

self.assertEqual(预期结果,实际结果)#判断预期结果是实际结果是否相等。
1.如果相等,用例通过
2.如果不等,用例不通过,抛出异常

self.assertIn(预期结果,实际结果)#判断预期结果是否包含在实际结果中
1.包含,用例通过
2.不包含,用例不通过,抛出异常

注意:在unittest中使用断言,都需要通过self.断言方法 来调用!

def test_username_password_ok(self):
    self.assertEqual(login('admin','123456') ,'登录成功')

    if login('admin','123456') == '登录成功':
        print('pass')
    else:
        print('fail')
def test_username_error(self):
    self.assertEqual(login('root', '123456'), '登录失败')
    self.assertIn('失败',login('root', '123456'))

    if login('root', '123456') == '登录失败':
        print('pass')
    else:
        print('fail')

七、参数化

在测试方法中,使用变量来替代具体的测试数据,然后使用传参的方法将测试数据传递给方法的变量。

好处:相似的代码不需要多次书写

工作中场景

  • 1、测试数据一般放在json文件中
  • 2、使用代码读取json文件,提取我们想要的数据——>[( ),( )]或[ [ ],[ ] ]

安装插件

  • unittest框架本身是不支持参数化,想要使用参数化,需要安装插件来完成。

  • 联网安装

    pip install parameterized
    
  • 验证

    pip list #查看到 parameterized
    from parameterized import pa...
    

步骤

  • 1.导包:unittest/pa...
  • 2.定义测试类
  • 3.书写测试方法(用到的测试数据使用变量代替)
  • 4.组织测试数据并传参(使用装饰器@parameterized.expand(data))
# 1.导包
#  unittest/pa...
import unittest
from parameterized import parameterized
from fixture import login
import json
#组织测试数据[(),(),()]or[[],[],[]]
def build_data():
    data = []
    with open('data.json',encoding='utf-8') as f:
        result=json.load(f)#[{},{},{}]
        for i in result:
            data.append((i['username'],i['password'],i['expect']))
    return data
# data =[
#     ('admin','123456','登录成功'),
#     ('admin','123','登录失败'),
#     ('root','123456','登录失败')
# ]
# 2.定义测试类
class TestLogin(unittest.TestCase):
    # 3. 书写测试方法(用到的测试数据使用变量代替)
    @parameterized.expand(build_data())# 4.组织测试数据并传参(装饰器 @)
    def test_login(self,username,password,expect):
        self.assertEqual(expect,login(username,password))

八、跳过

对于一些未完成的或者不满足测试条件测试函数和测试类,不想执行,可以使用跳过(使用装饰器完成)代码书写在TestCase里面

#直接将测试函数标记成跳过
@unittest.skip('跳过原因')
#根据条件判断测试函数是否跳过,判断条件成立,跳过
@unittest.skipIf(判断条件,'跳过原因')

示例

import unittest
version = 30
class TestDemo(unittest.TestCase):

    @unittest.skip('没什么原因,就是想跳过')
    def test_1(self):
        print("测试方法 1")

    @unittest.skipIf(version >=30,'如果版本号大于等于30就跳过')
    def test_2(self):
        print("测试方法 2")

    def test_3(self):
        print("测试方法 3")

九、测试报告

  • 自带的测试报告:只有单独运行TestCase的代码,才会生成测试报告

image.png

  • 生成第三方的测试报告HTMLTestRunner):
  1. 获取第三方的 测试运行类模块,将其放在代码的目录中

  2. 导包unittest

  3. 使用 套件对象,加载对象 去添加用例方法

  4. 实例化第三方的运行对象 并运行 套件对象

    HTMLTestRunner(参数)
    #stream=sys.stdout,必填,测试报告的文件对象(open),注意,是二进制文件需要用wb打开
    #verbosity=1,可选,报告的详细程度,默认1简略,2详细
    #title=none,可选,测试报告的标题
    #description=none,可选,描述信息,python的版本,pycharm版本
    

示例

# 1.获取第三方的  测试运行类模块,将其放在代码的目录中
from HTMLTestRunner import HTMLTestRunner
# 2. 导包unittest
import unittest
# 3.使用  套件对象,加载对象   去添加用例方法
suite = unittest.defaultTestLoader.discover('.','para*.py')#加载对象

# 4.实例化第三方的运行对象 并运行  套件对象
file= "E:\test\Unittest\report1.html"
with open(file,'wb') as f:
    runner = HTMLTestRunner(f,2,'测试报告','python 3.8')
    runner.run(suite)

image.png

查看测试报告

image.png

流程总结

1、组织用例文件(TestCase里边)书写参数化,书写断言,书写Fixture,书写 跳过如果是单个测试文件,直接运行,得到测试报告如果有多个测试文件,需要组织运行生成测试报告

2、使用 套件对象(TextSuite)组装,或者使用加载对象(TextLoader)组装

3、运行对象(TestRunner) 运行

1.运行对象 = 第三方的运行类(文件对象:使用wb方式打开)
2.运行对象.run(套件对象)