框架
说明:
1. 框架英文单词framework 2. 为解决一类事情的功能集合
> 需要按照框架的规定(套路) 去书写代码
什么是UnitTest框架?
概念:UnitTest是Python自带的一个单元测试框架,用它来做单元测试。
----
自带的框架(官方): 不需要单外安装, 只要安装了 Python,就可以使用
random, json, os, time
第三方框架: 想要使用 需要先安装后使用(pytest)
selenium , appium, requests
----
单元测试框架: 主要用来做单元测试, 一般单元测试是开发做的.
对于测试来说, unittest 框架的作用是 自动化脚本(用例代码) 执行框架(使用 unittest 框架 来 管理 运行 多个测试用例的)
为什么使用UnitTest框架?
1. 能够组织多个用例去执行
2. 提供丰富的断言方法(让程序代码代替人工自动的判断预期结果和实际结果是否相符)
3. 能够生成测试报告
UnitTest核心要素(unitest 的组成部分)
TestCase(最核心的模块)
TestCase(测试用例), 注意这个测试用例是 unittest 框架的组成部分, 不是手工和自动化中我们所说的用例(Test Case)
主要作用: 每个 TestCase(测试用例) 都是一个代码文件, 在这个代码文件中 来书写 真正的用例代码
TestSuite
TestSuite(测试套件), 用来 管理 组装(打包)多个 TestCase(测试用例) 的
TestRunner
TestRunner(测试执行,测试运行), 用来 执行 TestSuite(测试套件)的
TestLoader
TestLoader(测试加载), 功能是对 TestSuite(测试套件) 功能的补充,
管理 组装(打包)多个 TestCase(测试用例) 的
Fixture
Fixture(测试夹具), 书写在 TestCase(测试用例) 代码中, 是一个代码结构, 可以在每个方法执行前后都会执行的内容
举例:
登录的测试用例, 每个用例中重复的代码就可以写在 Fixture 代码结构中, 只写一遍, 但每次用例方法的执行,都会执行 Fixture 中的代码
1. 打开浏览器
2. 输入网址
1. 是一个代码文件, 在代码文件中 来书写真正的用例代码
2. 代码文件的名字必须按照标识符的规则来书写(可以将代码的作用在文件的开头使用注释说明)
步骤
1. 导包 (unittest)
2. 自定义测试类
3. 在测试类中书写测试方法
4. 执行用例
代码
"""
代码的目的: 学习 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. 代码文件的名字以数字开头
2. 代码文件名字中有空格
3. 代码文件名字有中文
4. 其他的特殊符号
(数字, 字母, 下划线组成, 不能以数字开头)
右
键运行没有 unittests for 的提示, 出现的问题
解决方案:
方案1. 重新新建一个代码文件, 将写好的代码复制进去
方案2. 删除已有的运行方式
测试方法中不是以 test_ 开头的, 或者单词写错了
TestSuite(测试套件): 管理 打包 组装 TestCase(测试用例) 文件的
TestRunner(测试执行): 执行 TestSuite(套件)
步骤
1. 导包(unittest)
2. 实例化(创建对象)套件对象
3. 使用套件对象添加用例方法
4. 实例化运行对象
5. 使用运行对象去执行套件对象
代码
TestSuite(测试套件): 是用来管理多个 TestCase(测试用例) 的,
先创建多个 TestCase(测试用例) 文件
"""
学习 TestSuite 和 TestRunner 的使用
"""
# 1. 导包(unittest)
import unittest
from hm_07_testcase1 import TestDemo1
from hm_07_testcase2 import TestDemo2
# 2. 实例化(创建对象)套件对象,
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'))
# 4. 实例化运行对象
runner = unittest.TextTestRunner()
# 5. 使用运行对象去执行套件对象
# 运行对象.run(套件对象)
runner.run(suite)
"""
学习 TestSuite 和 TestRunner 的使用
"""
# 1. 导包(unittest)
import unittest
# 2. 实例化(创建对象)套件对象,
from hm_07_testcase1 import TestDemo1
from hm_07_testcase2 import TestDemo2
suite = unittest.TestSuite()
# 3. 使用套件对象添加用例方法
# 方式二 将一个测试类中的所有方法进行添加
# 套件对象.addTest(unittest.makeSuite(测试类名))
# 缺点: makeSuite() 不会提示
suite.addTest(unittest.makeSuite(TestDemo1))
suite.addTest(unittest.makeSuite(TestDemo2))
# 4. 实例化运行对象
runner = unittest.TextTestRunner()
# 5. 使用运行对象去执行套件对象
# 运行对象.run(套件对象)
runner.run(suite)
1. 在 tools 模块中定义 add 函数, 对两个数字进行求和计算
2. 书写 TestCase 代码对 add() 进行测试
用例 1: 1, 2, 3
用例 2: 10, 20, 30
用例 3: 2, 3, 5
"""案例练习"""
# 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) == 30:
print('测试通过')
else:
print('测试不通过')
def test_method3(self):
# 1, 2, 3 判断实际结果和预期结果是否相符
if add(2, 3) == 5:
print('测试通过')
else:
print('测试不通过')
import unittest
# 实例化套件对象
from hm_08_test import TestAdd
suite = unittest.TestSuite()
# 添加测试方法
suite.addTest(unittest.makeSuite(TestAdd))
# 实例化执行对象
runner = unittest.TextTestRunner()
runner.run(suite)
TestLoader (测试加载), 作用和 TestSuite 的作用是一样的, 对 TestSuite 功能的补充,
用来组装测试用例的
比如: 如果 TestCase 的代码文件有很多, (10 20, 30 )
- 使用步骤
1. 导包
2. 实例化测试加载对象并添加用例 ---> 得到的是 suite 对象
3. 实例化 运行对象
4. 运行对象执行套件对象
在一个项目中 TestCase(测试用例) 的代码,一般放在一个单独的目录 (case)
"""TestLoader 的使用"""
# 1, 导包
import unittest
# 2, 实例化加载对象并添加用例
# unittest.TestLoader().discover('用例所在的路径', '用例的代码文件名')
# 用例所在的路径,建议使用相对路径, 用例的代码文件名可以使用 *(任意多个任意字符) 通配符
# suite = unittest.TestLoader().discover('./case', 'hm*.py')
# suite = unittest.TestLoader().discover('./case', '*test*.py')
# suite = unittest.TestLoader().discover('./case', '*test*')
suite = unittest.TestLoader().discover('./case', '*case1.py')
# 3, 实例化运行对象
# runner = unittest.TextTestRunner()
# # 4, 执行
# runner.run(suite)
# 可以将 3 4 步 变为一步
unittest.TextTestRunner().run(suite)
# 1. 导包
# 2. 使用默认的加载对象并加载用例
# 3. 实例化运行对象并运行
"""TestLoader 的使用"""
# 1, 导包
import unittest
# 2, 使用默认的加载对象并加载用例
suite = unittest.defaultTestLoader.discover('case', 'hm_*.py')
# 可以将 3 4 步 变为一步
unittest.TextTestRunner().run(suite)
Fixture(测试夹具) 是一种代码结构
在某些特定的情况下 会自动执行
在每个测试方法(用例代码) 执行前后都会自动调用的结构
# 方法执行之前
def setUp(self):
每个测试方法执行之前都会执行
pass
# 方法执行之后
def tearDown(self):
每个测试方法执行之后都会执行
pass
在每个测试类中所有方法执行前后 都会自动调用的结构(在整个类中 执行之前执行之后个一次)
# 类级别的Fixture 方法, 是一个 类方法
# 类中所有方法之前
@classmethod
def setUpClass(cls):
pass
# 类中所有方法之后
@classmethod
def tearDownClass(cls):
pass
模块: 代码文件
在每个代码文件执行前后执行的代码结构
# 模块级别的需要写在类的外边直接定义函数即可
# 代码文件之前
def setUpModule():
pass
# 代码文件之后
def tearDownModule():
pass
方法级别和类级别的 前后的方法,不需要同时出现,根据用例代码的需要自行的选择使用
1. 打开浏览器(整个测试过程中就打开一次浏览器) 类级别
2. 输入网址(每个测试方法都需要一次) 方法级别
3. 输入用户名密码验证码点击登录(不同的测试数据) 测试方法
4. 关闭当前页面(每个测试方法都需要一次) 方法级别
5. 关闭浏览器(整个测试过程中就关闭一次浏览器) 类级别
------
1. 打开浏览器(整个测试过程中就打开一次浏览器) 类级别
2. 输入网址(每个测试方法都需要一次) 方法级别
3. 输入用户名密码验证码点击登录(不同的测试数据) 测试方法
4. 关闭当前页面(每个测试方法都需要一次) 方法级别
2. 输入网址(每个测试方法都需要一次) 方法级别
3. 输入用户名密码验证码点击登录(不同的测试数据) 测试方法
4. 关闭当前页面(每个测试方法都需要一次) 方法级别
2. 输入网址(每个测试方法都需要一次) 方法级别
3. 输入用户名密码验证码点击登录(不同的测试数据) 测试方法
4. 关闭当前页面(每个测试方法都需要一次) 方法级别
5. 关闭浏览器(整个测试过程中就关闭一次浏览器) 类级别
import unittest
class TestLogin(unittest.TestCase):
def setUp(self):
"""每个测试方法执行之前都会先调用的方法"""
print('输入网址......')
def tearDown(self) -> None:
"""每个测试方法执行之后都会调用的方法"""
print('关闭当前页面......')
@classmethod
def setUpClass(cls) -> None:
print('------1. 打开浏览器')
@classmethod
def tearDownClass(cls) -> None:
print('------5. 关闭浏览器')
def test_1(self):
print('输入正确用户名密码验证码,点击登录 1')
def test_2(self):
print('输入错误用户名密码验证码,点击登录 2')
让程序代替人工自动的判断预期结果和实际结果是否相符.
断言的结果有两种:
> True, 用例通过
> False, 代码抛出异常, 用例不通过
在 unittest 中使用断言, 都需要通过 self.断言方法 来试验
self.assertEqual(预期结果, 实际结果) # 判断预期结果和实际结果是否相等
1. 如果相等, 用例通过
2. 如果不相等,用例不通过, 抛出异常
self.assertIn(预期结果, 实际结果) # 判断预期结果是否包含在实际结果中
1. 包含 ,用例通过
2. 不包含, 用例不通过, 抛出异常
assertIn('admin', 'admin') # 包含
assertIn('admin', 'adminnnnnnnn') # 包含
assertIn('admin', 'aaaaaadmin') # 包含
assertIn('admin', 'aaaaaadminnnnnnn') # 包含
assertIn('admin', 'addddddmin') # 不是包含
import unittest
from tools import login
class TestLogin(unittest.TestCase):
def test_username_password_ok(self):
"""正确的用户名和密码: admin, 123456, 登录成功"""
self.assertEqual('登录成功', login('admin', '123456'))
def test_username_error(self):
"""错误的用户名: root, 123456, 登录失败"""
self.assertEqual('登录失败', login('root', '123456'))
def test_password_error(self):
"""错误的密码: admin, 123123, 登录失败"""
self.assertEqual('登录失败', login('admin', '123123'))
def test_username_password_error(self):
"""错误的用户名和错误的密码: aaa, 123123, 登录失败"""
# self.assertEqual('登录失败', login('aaa', '123123'))
self.assertIn('失败', login('aaa', '123123'))
参数化 在测试方法中, 使用 变量 来代替具体的测试数据, 然后使用传参的方法将测试数据传递给方法的变量
好处: 相似的代码不需要多次书写.
工作中场景:
1. 测试数据一般放在 json 文件中
2. 使用代码读取 json 文件,提取我们想要的数据 ---> [(), ()] or [[], []]
unittest 框架本身是不支持 参数化, 想要使用参数化,需要安装插件来完成
- 联网安装(在 cmd 窗口安装 或者 )
pip install parameterized
------
pip 是 Python 中包(插件) 的管理工具, 使用这个工具下载安装插件
pip list # 查看到 parameterized
新建一个 python 代码文件, 导包验证
from pa... import pa...
1. 导包 unittest/ pa rameterized
2. 定义测试类
3. 书写测试方法(用到的测试数据使用变量代替)
4. 组织测试数据并传参
[
{
"desc": "正确的用户名和密码",
"username": "admin",
"expect": "登录成功"
},
{
"desc": "错误的的用户名",
"username": "root",
"expect": "登录失败"
},
{
"desc": "错误的的密码",
"username": "admin",
"expect": "登录失败"
}
]
# 1. 导包 unittest/ pa
import json
import unittest
from parameterized import parameterized
from tools import login
# 组织测试数据 [(), (), ()] or [[], [], []]
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
# 2. 定义测试类
class TestLogin(unittest.TestCase):
# 3. 书写测试方法(用到的测试数据使用变量代替)
@parameterized.expand(build_data())
def test_login(self, username, password, expect):
self.assertEqual(expect, login(username, password))
# 4. 组织测试数据并传参(装饰器 @)
对于一些未完成的或者不满足测试条件的测试函数和测试类, 不想执行,可以使用跳过
使用方法, 装饰器完成
代码书写在 TestCase 文件
# 直接将测试函数标记成跳过
@unittest.skip('跳过额原因')
# 根据条件判断测试函数是否跳过 , 判断条件成立, 跳过
@unittest.skipIf(判断条件, '跳过原因')
import unittest
# version = 30
version = 29
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 的代码,才会生成测试报告
1. 获取第三方的 测试运行类模块 , 将其放在代码的目录中
2. 导包 unittest
3. 使用 套件对象, 加载对象 去添加用例方法
4. 实例化 第三方的运行对象 并运行 套件对象
# 1. 获取第三方的 测试运行类模块 , 将其放在代码的目录中
# 2. 导包 unittest
import unittest
from HTMLTestRunner import HTMLTestRunner
# 3. 使用 套件对象, 加载对象 去添加用例方法
suite = unittest.defaultTestLoader.discover('.', 'hm_05_pa1.py')
# 4. 实例化 第三方的运行对象 并运行 套件对象
# HTMLTestRunner()
# stream=sys.stdout, 必填,测试报告的文件对象(open ), 注意点,要使用 wb 打开
# verbosity=1, 可选, 报告的详细程度,默认 1 简略, 2 详细
# title=None, 可选, 测试报告的标题
# description=None 可选, 描述信息, Python 的版本, pycharm 版本
# file = 'report.html' # 报告的后缀是.html
file = 'report1.html' # 报告的后缀是.html
with open(file, 'wb') as f:
# runner = HTMLTestRunner(f) # 运行对象
runner = HTMLTestRunner(f, 2, '测试报告', 'python 3.6.8 ') # 运行对象
# 运行对象执行套件, 要写在 with 的缩进中
runner.run(suite)
1. 组织用例文件(TestCase 里边), 书写参数化, 书写断言, 书写 Fixture, 书写 跳过, 如果单个测试测试文件, 直接运行, 得到测试报告, 如果有多个测试文件, 需要组装运行生成测试报告
2. 使用 套件对象组装, 或者使用 加载对象组装
3. 运行对象 运行
3.1 运行对象 = 第三方的运行类(文件对象(打开文件需要使用 wb 方式))
3.2 运行对象.run(套件对象)
import unittest
from HTMLTestRunnerCN import HTMLTestReportCN
# 组装用例方法
suite = unittest.defaultTestLoader.discover('.', '*pa1.py')
# 实例化运行对象
with open('report_cn.html', 'wb') as f:
runner = HTMLTestReportCN(f)
runner.run(suite)