一篇文章带你了解Python基础测试工具——UnitTest

发布时间 2023-11-12 09:16:22作者: 秋落雨微凉

一篇文章带你了解Python基础测试工具——UnitTest

测试人员一般使用Python作为主语言脚本来进行自动化开发,而Python自带的UnitTest脚本通常就是测试人员首先掌握的

那么本篇文章我们将来介绍Python的最基本自动化工具UnitTest来开始我们自动化的第一步

我们这篇文章将从以下角度进行讲解:

  • UnitTest基本介绍

  • UnitTest组成部分

  • UnitTest进阶部分

UnitTest基本介绍

首先我们将会对自动化和UnitTest进行一个基本的介绍

自动化脚本介绍

首先我们需要知道什么叫做自动化脚本:

  • 采用代码的形式书写项目,采用项目运行的方式来代替人工测试

自动化脚本的作用主要有以下几点:

  • 解放人力资源,杜绝人为性错误,加快测试速度
  • 使没有代码基本的人可以借助工具快速上手进行自动化测试

UnitTest基本介绍

下面我们来简单介绍一下UnitTest:

  • UnitTest是Python自带的一款单元测试框架,无需安装即写即用

我们来针对上面几个词汇进行讲解:

# 自带框架(官方框架)和第三方框架
# 自带框架:跟随Python官方一共上架的框架,只需要下载Python就可以直接使用,例如Python的UnitTest,OS,requests等
# 第三方框架:由非官方开发人员所开发的可以在Python上导入并使用的框架,例如我们后来会学到的pytest,httpRunner等

# 单元测试
# 单元测试一般是只单单针对后端功能进行测试,而自测环节通常是由后端进行的
# 但是我们测试人员也可以在后端开发期间或前后端联调阶段去进行单元测试以确定后端功能来减少提测时的错误率

最后我们简单介绍一下UnitTest的优点:

  • 能够组织多个⽤例去执⾏
  • 提供丰富的断⾔⽅法
  • 能够⽣成测试报告

UnitTest组成部分

下面我们来正式学习UnitTest的组成部分,来真正学习这个框架

UnitTest组成概述

我们首先在这里简单介绍一下UnitTest的整体组成部分:

# TestCase
# 简称:测试用例(核心模块)
# TestCase是UnitTest里面的一个类名,我们需要创建一个类去继承该TestCase
# 然后我们需要在该类型中书写测试方法def,后续我们就可以直接去执行该TestCase中的Case或采用其他方法集中执行

# TestSuite
# 简称:测试套件
# 正常来说我们的TestCase只能在当前类中去进行执行,如果我们的Case划分写在不同TestCase下,那么我们就不能一同执行
# 但是我们可以采用TestSuite将某个TestCase中的Case加入到该TestSuite中,然后采用执行器一同执行

# TestLoader
# 简称:测试加载器
# TestLoader其实和TestSuite是一样的作用
# 但不同的是TestSuite一次只能添加一个TestCase中的一个Case或一个Testcase的所有Case
# 但是TestLoader可以根据匹配定理来一次性添加多个同一文件夹下的Testcase来实现快速添加的功能

# TestRunner
# 简称:测试执行器
# TestRunner的主要功能就是为了执行TestSuite和TestLoader从而获得对应的测试数据

# Fixture
# 简称:测试夹具
# Fixture的主要功能就是在测试方法执行的前后去添加一些前置条件,比如打开浏览器,打开某个网页等
# Fixture主要被划分为三个级别:文件级别,类级别,方法级别,我们都在下面一一讲述的

TestCase

首先我们来学习UnitTest的核心点TestCase:

# 首先我们来概述TestCase
# TestCase就是一个类,一个需要被我们所继承的类
# TestCase的使用我们下面将采用一个用例来讲解

# 首先我们如果想要TestCase,我们需要导入UnitTest
import unittest

# 然后我们需要自定义一个类,这个类需要继承Testcase
class TestDemo(unittest.TestCase):
    # 下面我们在该类中所书写的每个方法都会变成一个case,在执行过程中被我们所执行
    # 此外我们还需要注意:我们的每个方法都必须以test_开头,否则系统不会将他判断为case
    
    def test_method1(self):
 		print('测试⽅法 1')
        
    def test_method1(self):
 		print('测试⽅法 2')
        
# 正常来说我们的TestCase是可以单独去执行的
# 如果我们想执行该TestCase下的所有方法,我们可以将光标对准Class行,右键run执行,从而获得结果
# 如果我们想执行该TestCase下的某个方法,我们可以将光标对准该def行,右键run执行,从而获得结果

TestRunner&TestSuite&TestLoader

下面我们来一起介绍这三个组件,因为他们之间有非常紧密的联系:

# 首先我们先来介绍TestRunner
# 该类就是一个单纯的执行机器,我们只需要创建对应的实体,然后调用该实体的run方法来执行对应的Case组件即可

# 我们先来介绍TestSuite

# 首先我们需要导入unittest
import unittest
# 我们需要注意,如果我们的TestSuite引用到了其他TestCase类的case方法,那么我们需要手动import导入对应类
# 当然这里推荐alt+enter自动导入
from testcase1 import TestDemo1
from testcase2 import TestDemo2
# 然后下面的操作我们可以单独列在一个main方法中,也可以直接书写
if __name__ == '__main__':
    # 首先我们创建一个TestSuite
    suite = unittest.TestSuite()
    # 然后我们需要采用一种格式来添加case到该suite中:suite.addTest(类名('方法名'))
    suite.addTest(TestDemo1('test_method1'))
	suite.addTest(TestDemo1('test_method2'))
	suite.addTest(TestDemo2('test_method1'))
	suite.addTest(TestDemo2('test_method2'))
    # 当然我们的suite也是存在一种快速导入一个TestCase的所有Case的方法:suite.addTest(unittest.makeSuite(类名))
    # 但是我们需要注意makeSuite方法是没有提示信息的,因为该方法其实是unittest之前版本就想要删除的方法,但是推荐使用
    suite.addTest(unittest.makeSuite(TestDemo1))
	# 最后我们创建一个TestRunner去执行
    runner = unittest.TestRunner()
    runner.run(suite)
    
# 下面我们来介绍TestLoader

# 首先我们需要导入unittest
import unittest
# 然后下面的操作我们可以单独列在一个main方法中,也可以直接书写
if __name__ == '__main__':
    # 同理我们需要创建一个TestLoader对象,当然我们也可以直接在创建过程中使用方法然后直接返回TestLoader对象
    # TestLoader有这么一个方法:unittest.TestLoader().discover('用例所在的路径', '用例的代码文件名')
    # 该用例的代码文件名是可以支持*作为任意字符数,也可以使用?来代替单个字符数
    suite = unittest.TestLoader().discover('./case', '*case*.py')
    # 同理我们需要一个TestRunner来执行run方法,当然也可以化作一步来执行
    unittest.TestRunner().run(suite)

Fixture

最后我们来介绍一下Fixture测试夹具:

# 首先我们需要知道Fixture测试夹具的主要用途就是做一些前置条件或者结束条件,它会在某些特定条件下执行

# 首先是方法级别的Fixture测试夹具
# 它是在每个测试方法(用例代码) 执行前后都会自动调用的结构
# 方法执行之前
	def setUp(self):
		每个测试方法执行之前都会执行
		pass
# 方法执行之后
	def tearDown(self):
		每个测试方法执行之后都会执行
		pass
    
# 然后是针对类级别的Fixture测试夹具
# 它是在每个测试类中所有方法执行前后 都会自动调用的结构(在整个类中执行之前或之后执行一次)
# 需要注意:类级别的Fixture 方法, 是一个 类方法
# 类中所有方法之前
    @classmethod
    def setUpClass(cls):
    	pass
# 类中所有方法之后
	@classmethod
	def tearDownClass(cls):
		pass
    
# 最后是针对模块级别的Fixture测试夹具
# 在每个代码文件执行前后执行的代码结构
# 需要注意:模块级别的需要写在类的外边直接定义函数即可
# 代码文件之前
	def setUpModule():
		pass
# 代码文件之后
	def tearDownModule():
		pass
    
# 下面我们采用一个用户账户登录的用例来简单展示一下Fixture

import unittest

# 这些Fixture并没有必要一次性全部书写执行,只需要书写自己所需要的部分即可
# 例如下述的这个Moudle层级的Fixture其实是没有必要执行的,我们可以直接将他省略掉

"""""
# 代码文件之前
def setUpModule():
	print("文件执行前")
# 代码文件之后
def tearDownModule():
	print("文件执行后")
"""""

class TestLogin(unittest.TestCase):
    
    # 在执行该类前所需要调用的方法
    @classmethod
    def setUpClass(cls) -> None:
    	print('------打开浏览器')
    
    # 在执行该类后所需要调用的方法
    @classmethod
    def tearDownClass(cls) -> None:
    	print('------关闭浏览器')
    
    # 每个测试方法执行之前都会先调用的方法
    def setUp(self):
    	print('输入网址......')
    
    # 每个测试方法执行之后都会调用的方法
    def tearDown(self) -> None:
    	print('关闭当前页面......')
    
    # 测试Case1
    def test_1(self):
    	print('输入正确用户名密码验证码,点击登录 1')
  
	# 测试Case2
    def test_2(self):
    	print('输入错误用户名密码验证码,点击登录 2')

UnitTest进阶部分

最后我们介绍UnitTest中的一些进阶知识点

断言

既然讲到Python,那么断言必然是我们所需要了解的部分:

  • 断言就是为了让程序代替人工自动的判断预期结果和实际结果是否相符

断言的结果只分为两种:

  • True:用例通过
  • False:用例不通过,并且抛出异常

我们在这里给出Python中所有经常使用到断言语句:

断言语法 断言意义
assertEqual(预期结果,实际结果) 判断是否相等
assertNotEqual(预期结果,实际结果) 判断是否不等
assertTrue(实际结果) 判断是否为True
assertFalse(实际结果) 判断是否为False
assertIsNone (实际结果) 判断是否为None
assertIsNotNone(实际结果) 判断是否不为None
assertIn(预期结果,实际结果) 判断是否包含

我们给出一个简单的案例来展示断言功能:

# 首先我们需要知道断言其实就是一个判断语句,用来判断assert后面跟随的内容是否为True
# 例如我们简单的计算或比较其实也是一个断言,不过上面的断言语句是我们封装后的结果

# 我们需要注意:如果我们使用到上面封装的assert方法,那么我们需要采用self进行调用

import unittest
from tools import login

class TestLogin(unittest.TestCase):
    
    # 其实下面的assert就是一个最基本的断言判断
    def test_username_password_assert_ok(self):
        res = login('admin', '123456')
    	assert res == "登录成功"
    
    # 判断登录:正确的用户名和密码: admin, 123456, 登录成功
    def test_username_password_ok(self):
    	self.assertEqual('登录成功', login('admin', '123456'))
    
    # 判断登录:错误的用户名: root, 123456, 登录失败
    def test_username_error(self):
    	self.assertEqual('登录失败', login('root', '123456'))
    
    # 判断登录结果是否包含“失败”,若包含即为成功
    def test_username_password_error(self):
        self.assertIn('失败', login('aaa', '123123'))

参数化

下面我们来介绍UnitTest中的参数化:

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

  • 用于减少代码的重复书写,减少我们的复杂操作

我们在工作中一般会将数据存放在JSON文件中或者从网页中直接导出对应的JSON文件

下面我们来介绍参数化的两种方法:

# unittest 框架本身是不支持 参数化, 想要使用参数化,需要安装插件来完成
# console控制台下载:pip install parameterized

# 参数化方法1

import unittest
from parameterized import parameterized
# 首先我们的参数可以存在放类中
# 我们一般采用列表中存在元组或列表来进行存储:[(),(),()]或[[],[],[]]
data = [
    ('admin','123456','登陆成功'),
    ('admin','123123','登陆失败'),
    ('errorAdmin','123456','登陆失败'),
]

# 然后我们就可以定义参数化直接调用该data
class TestLogin(unittest.TestCase):
    # 采用注解进行注入
    @parameterized.expand(data)
    def test_login(self,username,password,expect):
        self.assertEqual(expect, login(username, password))
        
        
# 参数化方法2

# 首先我们需要单独创建一个JSON类型的文件进行数据存储
[
        {
        "desc": "正确的用户名和密码",
        "username": "admin",
        "password": "123456",
        "expect": "登录成功"
    },
    {
        "desc": "错误的的用户名",
        "username": "root",
        "password": "123456",
        "expect": "登录失败"
    },
    {
        "desc": "错误的的密码",
        "username": "admin",
        "password": "123123",
        "expect": "登录失败"
    }
]

# 然后我们就可以到TestCase中去书写参数化

# 首先我们需要导入该Json文件以及相关类
import json
import unittest
from parameterized import parameterized
from tools import login

# 然后我们需要有一个单独的方法来获取json文件并返回一个类似于我们前面所定义的date:
def build_data():
	with open('data.json', encoding='utf-8') as f:
		result = json.load(f) # [{}, {}, {}]
		data = []
		for i in result: # i {}
			data.append((i.get('username'), i.get('password'), i.get('expect')))
	return data

# 剩下的部分其实就和参数化1的操作相同的
class TestLogin(unittest.TestCase):
    # 采用注解进行注入
    @parameterized.expand(build_data())
    def test_login(self,username,password,expect):
        self.assertEqual(expect, login(username, password))

跳过测试

我们在前面的TestSuite或TestLoader里面一次性获得所有Case并执行,但有时我们也许不需要执行某些Case:

  • 对于一些未完成的或者不满足测试条件的测试函数和测试类, 不想执行,可以使用跳过

我们下面直接来介绍相关语法:

# 关于跳过测试其实就是一条注解我们就可以实现
# 其具体语法如下

# 直接将测试函数标记成跳过
@unittest.skip('跳过原因')

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

# 我们给出一条简单的案例
import unittest

money = 29

class TestDemo(unittest.TestCase):
    
    @unittest.skip('无跳过原因')
    def test_1(self):
    	print('跳过测试')
    
    @unittest.skipIf(version >= 30, '剩余金钱不足,无法购买')
    def test_2(self):
        money -= 30
    	print('剩余金钱足够,可以购买')
        
    def test_3(self):
		print('可执行方法')

结束语

这篇文章中详细介绍了Python自带的测试框架,因为是自带框架,内容相对比较简单,后续我们会学习pytest来加强自动化开发能力

附录

下面给出我学习和书写该篇文章的一些参考文章,大家也可以去查阅:

  1. 黑马课程:11.Python-unittest 的组成_哔哩哔哩_bilibili

  2. 菜鸟教程:Python 基础教程 | 菜鸟教程 (runoob.com)