Python-UnitTest框架
一、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(套件对象)