一、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(测试用例)
- 是一个代码文件,在代码文件中,用来书写真正的用例代码。
- 代码文件的名字必须按照标识符的规则来书写,可以将代码的作用在文件的开头使用注释说明
- 步骤:
-
① 导包(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 放到方法名后面,只执行当前的方法```
- 代码书写常见错误:
- 代码文件命名不规范
- 代码运行没结果(右键运行没有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)
运行结果注意:
练习:
#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断言方法:
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的代码,才会生成测试报告
- 生成第三方的测试报告(HTMLTestRunner):
-
获取第三方的 测试运行类模块,将其放在代码的目录中
-
导包unittest
-
使用 套件对象,加载对象 去添加用例方法
-
实例化第三方的运行对象 并运行 套件对象
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)
查看测试报告:
流程总结
1、组织用例文件(TestCase里边),书写参数化,书写断言,书写Fixture,书写 跳过,如果是单个测试文件,直接运行,得到测试报告,如果有多个测试文件,需要组织运行生成测试报告
2、使用 套件对象(TextSuite)组装,或者使用加载对象(TextLoader)组装
3、运行对象(TestRunner) 运行
1.运行对象 = 第三方的运行类(文件对象:使用wb方式打开)
2.运行对象.run(套件对象)