https://blog.csdn.net/weixin_45912307/article/details/124078581
1. 什么是单元测试?
2. 单元测试什么时候执行?
3. 单元测试由谁负责?
4. python的主要单元测试框架?
Unittest
:python内置的标准类库;Pytest
:丰富、灵活的测试框架,语法简单,可以结合allure生成一个炫酷的测试报告;Nose
:是对unittest的扩展,使得python的测试更加简单。Mock
:unittest.mock是用来测试python的库1. Unittest提供了了test cases
、test suites
、test fixtures
、test runner
相关的组件
2. 编写规范
import unittest
unittest.TestCase
1. 类方法
setUpClass()
只会在测试用例类执行执行一次tearDownClass()
只会在测试用例类执行后执行一次setUpClass()
与tearDownClass()
。比如:数据库连接及销毁、cookie获取2. 函数方法
setUp()
: 每次测试用例方法执行之前都会执行的方法。tearDown()
:每次用例方法执行之后都会自动执行的方法setUp
用来为测试准备环境;tearDown
用来清理环境。1. 模块名:以test开头
2. 类名:以Test开头
3. 方法名:以test开头
@unittest.skip
1.断言
self.assertEqual(expected, actual)
:提示更加具体,会把预期结果和实际结果打印出来self.assertTrue(expected == actual)
:预期结果和实际结果没有具体提示2. 断言结果
1.方式1:
discover
可以一次调用多个脚本start_dir
被测试脚本的路径pattern
脚本名称匹配规则2.方式2: 只测试某个具体的模块、功能,使用 loaderTestsFromTestCase
、loaderTestsFromModule
3.方式3:加载指定测试类 loader.loadTestsFromTestCase(测试用例类名)
import os
import time
import unittest
# 1. 初始化testloader(加载器)
loader = unittest.TestLoader()
# 2.1 加载方式1: 加载全部测试用例
# 2.1 suite = testloader.discover(文件夹路径,'test_*.py) 发现(加载)用例
# 2.2 加载方式2:加载多个模块测试用例
# 加载多个模块的测试用例,保存到测试套件当中
suite1 = loader.loadTestsFromModule(模块名)
suite2= loader.loadTestsFromTestCase(类名)
suite3 = loader.loadTestsFromName(name,模块名) # name:传入一个模块或测试用例类或测试方法,或一个可调用的对象
suite_list = [suite1,suite2,suite3]
# 将多个测试套件合并添加第一个总的测试套件中,初始化一个空的测试套件
total_suite = unittest.TestSuite()
total_suite.addTests(suite_list)
# suite.addTests([类名1('函数名1'),类名1('函数名2'),类名1('函数名3'),类名2,模块名1,模块名2,....])
# 加载方式3:加载指定测试类
# suite = loader.loadTestsFromTestCase(测试用例类名)
# 3. 初始化运行器
runner = unittest.TextTestRunner()
# 4. 运行测试用例 runner.run(suite)
runner.run(total_suite)
# 模块导入
from unittestreport.HTMLTestRunnerNew import HTMLTestRunner
# 报告存放路径
report_path = os.path.join(root_path,'report')
if not os.path.exists(report_path):
os.mkdir(report_path)
# 报告名称
ts = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
file_name = 'api_test_{}.html'.format(ts)
file_path = os.path.join(report_path,file_name)
with open(file_path,'w',encoding='utf8') as f:
# 初始化运行器,以普通文本生成测试报告 TextTestRunner
runner = HTMLTestRunner(
stream=f,
verbosity=2,
title='xxx系统测试报告',
description='接口自动化测试报告',
tester='jsonLiu'
# 6.运行测试用例 runner.run(suite)
runner.run(total_suite)
)
1. unittest 右键 --> run ‘模块名|方法名|类名’,pycharm自带
2. python 代码 main方式:unittest.main()
3. 命令行方式运行python -m unittest
1. 遵循 ascii 编码排序:
TestCase
TestLoader
加载TestCase
到TestSuite
TextTestRunner
来运行TestSuite
TextTestResult
中unittest.main
模块中import unittest
class TestUnittestDemo(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("setUpClass")
def setUp(self) -> None:
print("setUp method")
def tearDown(self) -> None:
print("tearDown method")
@classmethod
def tearDownClass(cls) -> None:
print("tearDownClass")
def test_add(self):
a = 1
b = 2
print("%s-> %s" % (self.__repr__, a + b))
def test_subtraction(self):
a = 1
b = 2
print("%s-> %s" % (self.__repr__, a - b))
def test_sum(self):
print("%s-> %s" % (self.__repr__, sum([i for i in range(100)])))
class TestUnittestDemo02(unittest.TestCase):
def test_mul(self):
print("%s-> %s" % (self.__repr__, 10*20))
if __name__ == '__main__':
# import unittest
import os
import time
from unittestreport.HTMLTestRunnerNew import HTMLTestRunner
# suite = unittest.defaultTestLoader.discover('./test_case', 'test*.py')
loader = unittest.TestLoader()
suite1 = loader.loadTestsFromName('test_add','test_unittest_demo')
suite2 = loader.loadTestsFromModule("test_unittest_demo",pattern='test_subtraction')
suite3 = loader.loadTestsFromTestCase(TestUnittestDemo02)
suite = [suite1,suite2,suite3]
local = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
report_path = os.path.join(local, 'report')
if not os.path.exists(report_path):
os.mkdir(report_path)
ts = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
file_name = 'api_test_{}.html'.format(ts)
file_path = os.path.join(report_path, file_name)
with open(file=file_path, mode='w', encoding='utf8') as f:
runner = HTMLTestRunner(stream=f, verbosity=2, title='xxx项目接口测试报告', description='xxx接口用例', tester='jsonLiu')
runner.run(suite)
1. pytest是一个非常成熟的全功能的Python测试框架
2. 官方文档: https://docs.pytest.org/en/7.1.x/index.html
pip install -U pytest U
升级并安装pip install pytest-sugar
运行界面美化pip install pytest-rerunfailures
用例失败重试运行pip install pytest-xdist
多任务并发执行测试用例pip install pytest-assume
允许pytest测试用例中执行多个失败的断言pip intall pytest-html
生成html报告pip list
查看pytest -h
帮助1. 测试文件
test_* .py
*_test.py
2. 用例识别
Test*
类包含的所有test_*
的方法(测试类不能带有__init__
方法);test_*
方法3. pytest也可以执行unittest框架写的用例和方法
4. 终端执行
pytest /py.test
pytest -v
: (最高级别信息–verbose)打印详细运行日志信息pytest -v -s 文件名
:s是带控制台输出结果,也是输出详细pytest 文件名.py
:执行单独一个pytest模块pytest 文件名.py::类名
:运行某个模块里面某个类pytest 文件名.py::类名:方法名
:运行某个模块里面某个类里面的方法pytest -v -k "类名 and not 方法名"
:跳过运行某个用例pytest -m [标记名]
@pytest.mark.[标记名]
pytest -x 文件名.py
:一旦运行到报错就停止运行pytest --maxfail=[num]
:当运行错误达到num的时候就停止运行5. pycharm方式执行
Preferences--->Tools--->Python Integrated Tools
,将Testing里的Default test runner选择项选为pytest,保存即可。pytest -v -s 文件名.py::测试类名::测试函数名
pytest.main()
运行所有用例pytest.main("-v -s 模块名::类名")
参数和终端方式一样pytest.main(["-v", "模块名", "类名","函数名"])
1. 场景:
2. 安装`:
3. 执行方式:
pytest --reruns 失败重运行次数 -v -s 模块名.py
pytest -v --reruns 失败重运行次数 --reruns-delay 延时时间(单位:s)
pytest.main()
pytest.ini
文件,添加失败重试参数,即:addopts = -s --reruns 2 --html=./report.html
-s
:是输出程序运行信息--rerun n
: 失败重试n次html=./report.html
: 在当前目录下生成report.html
文件1. 场景:
2. 安装:
3. 执行:
1. 作用
@pytest.fixture()
,它是在声明一个函数是fixture,如果测试函数参数列表中含有fixture名,那么pytest执行的时候就会检测到,并在测试函数运行之前执行该fixture2.用法: 在方法前面加@pytest.fixture()
@pytest.fixture()
默认函数级别@pytest.fixture(autouse=True)
# 设置为默认启动形式@pytest.fixture(scope="function|class|module", autouse=True)
自定义作用范围3.语法:
@pytest.fixture(scope ="function",params=None,autouse=False,ids=None,name=None)
4.参数解读:
scope
:被标记方法的作用域
autouse
:默认False,若为True,刚每个测试函数都会自动调用该fixture,无需传入fixture函数名params
:可选形参列表,支持列表传入,默认None,每个param的值fixture都会去调用执行一次ids
:用例标识id,默认为空,如果没有提供id,它们将从参数中自动生成。name
:fixture的重命名,通常来说使用 fixture 的测试函数会将 fixture 的函数名作为参数传递,但是 pytest 也允许将fixture重命名,如果使用了name,函数名不再生效5.fixture调用
@pytest.mark.usefixtures("定义的fixture名称")
作为函数应用@pytest.mark.usefixtures("fixture函数名称")
将fixture函数名称作为用例参数def test_case(fixture函数名称)
fixture函数名称作为用例参数@pytetst.mark.usefixtures
@pytest.mark.usefixtures
6.1 应用场景1:fixture
测试用例执行时,有的用例需要登陆才能执行,有些用例不需要登陆。setup和teardown无法满足。fixture可以。默认scope(范围)
@pytest.fixture()
6.2 应用场景2: conftest.py 配置文件
与其他测试工程师合作一起开发时,公共模块要在不同文件中,要在大家都能访问到的地方。
解决:
conftest.py
这个文件进行数据共享,并且可以放在不同位置起着不同的范围共享作用。执行:
步骤:
@pytest.fixture
写在conftest.py
注意事项
:
conftest
固定文件名,不能替换conftest.py
与运行的用例需在同一个package下,并且有_init__.py
文件。import conftset
,直接调用fixture名称就可以6.3 应用场景3: yield关键字
测试方法后销毁清除数据或环境清理工作
@pytest.fixture(scope=module)
addfinalizer
6.4 应用场景4:autouse=True 自动调用
不想原测试方法有任何改动,或全部都自动实现自动应用,没特例,也都不需要返回值时可以选择自动应用
autouse=True
实现。@pytest.fixture(autouse=True)
@pytest.mark.usefixtures("函数名")
6.5 应用场景5 params 数据参数化
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
参数方式
@pytest.mark.parametrize('参数名',list)
@pytest.mark.parametrize('参数名1,参数名2',[(参数1_data[0], 参数2_data[0]),(参数1_data[1], 参数2_data[1])])
@pytest.mark.parametrize("params1", ["user01"])
@pytest.mark.parametrize("params2", [["value_p2","value_p3"],["value_pp2,value_pp3"]])
def test_func01(params1,params2):
d = {}
d[params1] = params2
函数方式
@pytest.fixture(scope='作用范围')
def 函数名(request):
data = request.param # 获取对象属性
return request
参数 = [{'user': 'admin01', 'password': '123456'}, {'user': 'admin02', 'password': 'abc123'}]
@pytest.mark.parametrize('函数名',参数,indirect=True) # indirect默认为False,需要设置成Ture以函数方式传参
def test_函数名(函数名):
a = login
print(a)
1. 使用场景
2. 解决:
@pytest.mark.skip("注释说明")
@pytest.mark.skipif(condition, reason=None)
在满足某些条件下才希望通过,否则跳过这个测试。
condition
: 跳过的条件,必传参数reason
: 标注原因1. 使用场景:
pytest.mark.xfail
),它是一个xpass,将在测试摘要中报告。2. 解决:
@pytest.mark.xfail(condition=None, reason=None, raises=None, run=True, strict=False)
3. 常用参数:
condition
:预期失败的条件,必传参数reason
:失败的原因1. 注册标签名
方式1:创建pytest.ini
文件,在文件中按如下形式添加标签名:
###################### 打标签 #######################
[pytest]
markers=
标签名: The explanation must be in Chinese
标签名1
标签名2
# : 代表解释,必须为英文
############## 配置pytest命令行运行参数 ##################
[pytest]
addopts = -v -s --reruns 失败次数 --html=./report.html
# 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数
################### 配置测试搜索的路径 ####################
[pytest]
testpaths = ./scripts
# 当前目录下的scripts文件夹 可自定义
################### 配置测试搜索的文件名 ####################
[pytest]
python_files = test_*.py
# 当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件 -可自定义
################### 配置测试搜索的测试类名 ####################
[pytest]
python_classes = Test_*
# 当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类
################### 配置测试搜索的测试函数名 ####################
[pytest]
python_functions = test_*
# 当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的⽅法 -可自定义
方式2:创建conftest.py
文件
# 注册标签方式2:
def pytest_configure(config):
marker_list = ["标签名1","标签名2",...]
for markers in marker_list:
config.addinivalue_line(
"markers",markers
)
2. 打标记
@pytest.mark.已注册的标记名
class TestClass(object):
pytestmark = pytest.mark.已注册标签名
pytestmark = [pytest.mark.标签1, pytest.mark.标签2] # 多标签模式
import pytest
pytestmark = pytest.mark.webtest
pytestmark = [pytest.mark.标签1, pytest.mark.标签2] # 多标签模式
3. 运行过滤:pytest.main(['-m','标签名'])
1. 安装
pip install pytest-html -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
2. 使用方式
pytest --html=存储路径/report.html
pytest.ini
文件,添加报告参数,即:addopts = -s --html=./report.html
-s
是输出程序运行信息--html=./report.html
在当前目录下生成report.html文件--html=./report.html
改成 --html=./report.xml
1. allure安装链接:
https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/
2. 与pytest集成:需要pytest执行用例后,生成allure能够解析的测试结果文件
pip install allure-pytest
3. 使用:
pytest --alluredir=测试报告目录
allure serve alluredir的路径
1. 默认情况:根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z
pytest用例执行基本原则:根据名称的字母逐一进行ASCII比较,越小越先执行。
2. 作用: 以函数修饰符的方式标记被测函数,通过参数控制函数执行顺序
3. 安装:
pip install pytest-ordering -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
4. 使用方法:
@pytest.mark.run(order=x)
1. 使用场景:
2. 解决:
3. 安装:
pip install pytest-xdist -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
4. 执行
pytest -n 并行数
1. setup,teardown
setup_module
/teardown_module
):模块始末,全局的(优先最高)setup_function
/teardown_function
):只对函数用例生效(不在类中)setup_class
/teardown_class
):只在类中前后运行(在类中)。setup_method
/teardown_methond
):开始于方法始末(在类中)setup
/teardown
):运行在调用方法的前后2. conftest.py 文件:存放
3. pytest.ini 文件
4. run.py :项目入口文件
unittest | pytest | |
---|---|---|
用例编写规则 | 1)测试文件必须先import unittest 2)测试类必须继承 unittest.TestCase 3)测试方法必须以“test_”开头 4)测试类必须要有 unittest.main() 方法 |
1)测试文件名必须以“test_”开头或者"test"结尾(如:test_ab.py) 2)测试方法必须以“test”开头 3)测试类命名以"Test"开头 |
用例分类执行 | 默认执行全部用例,也可以通过加载testsuit,执行部分用例 | 可以通过@pytest.mark 来标记类和方法,pytest.main() 加入参数(“-m”)可以只运行标记的类和方法 |
用例前置和后置 | 提供了setUp/tearDown,只能针对所有用例 | pytest中的fixture显然更加灵活。可以任意自定义方法函数,只要加上@pytest.fixture() 这个装饰器,那么被装饰的方法就可以被使用 |
参数化 | 需依赖ddt库 | 使用@pytest.mark.parametrize() 装饰器 |
断言 | 很多断言格式(assertEqual、assertIn、assertTrue、assertFalse) | 1)assert 表达式 2) pytest.assume(断言) 支持运行多条失败断言 |
报告 | 使用HTMLTestRunnerNew库 | 有pytest-HTML、allure插件 |
失败重跑 | 无此功能 | pytest支持用例执行失败重跑,pytest-rerunfailures插件 |
https://github.com/JSonliuJ/pytestdemo_v1