单元测试框架是指在软件开发当中,针对软件的最小单位(函数、方法)进行正确性的检查测试。
java: junit 和 pytest
pytest: unittest 和 pytest
提高测试效率,降低维护成本
减少人工干预、提高测试的准确性,增加代码的重用性
核心思想是让不懂代码的人也能够通过这个框架去实现自动化测试
单元测试框架:只是自动化测试框架的组成部分之一
1、pytest是一个非常成熟的python单元测试框架,比unittest更灵活、容易上手
2、pytest 可以和selenium、request、appium结合实现web自动化、接口自动化、app自动化
3、pytest可以实现测试用例的跳过以及 reruns失败用例重试
4、pytest可以和allure生成非常美观的测试报告
5、pytest可以Jenkins持续集成
5、pytest有很多非常强大的插件、并且这些插件能够实现很多的实用的操作
一次性安装上面插件的方法:
将这些插件的名称放在 requirements.txt 文件中,使用命令 pip install -r requirements.txt
安装即可:
pytest
pytest-html
pytest-xdist
pytest-ordering
pytest-rerunfailures
allure-pytest
1、模块名必须以 test_ 开头或者 _test 结尾
2、测试类必须以 Test 开头,并且不能含有 __init__
方法
3、测试方法必须以test开头
pytest.main()
pytest.main(['-vs', 'test-login.py'])
pytest.main(['-vs', './interface_testcase'])
pytest
pytest -vs test_login.py
pytest -vs ./interface_testcase
-s 和 -v 参数案例
假如我们现在有一个项目 pytestdemo,所有的用例都存放在 testcase目录中
假设登录和登出的测试用例分别如下:
# test_login.py
lass TestLogin:
def test_login(self):
print("登录测试")
# test_logout.py
class TestLogout:
def test_logout(self):
print("登出测试")
运行所有模块的测试用例
如果要同时运行 test_login.py 和 test_logout.py 里面的所有测试用例,可以在testcase目录中新建一个 all.py 文件:
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs'])
pytest.main(['参数'])
里面的列表中-vs
参数表示输出详解的信息,包括测试用例中的print函数的输出信息,输出结果会看到以下内容运行单个模块的测试用例
假设现在只想运行 test_login.py 里面的测试用例,只需在 pytest.main()
添加如下参数即可:
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'test_login.py'])
运行指定目录的测试用例
假设该项目下有一个专门用于做接口测试的包 interface_testcase,那我如何只运行接口测试里面的测试用例,不运行 testcase 里面的测试用例呢?
把 all.py 移动至项目根目录下,并将要运行的目录作为参数传入main函数里面:
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'interface_testcase'])
这样就只会运行接口测试的内容了。
通过 nodeid 来指定要执行的测试用例
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'interface_testcase/test_interface.py::TestInterface::test_interface'])
-n 参数案例1
运行 testcase 里面的测试用例:
# test_login.py
import time
class TestLogin:
def test_login(self):
time.sleep(3)
print("登录测试")
# test_logout.py
import time
class TestLogout:
def test_logout(self):
time.sleep(3)
print("登出测试")
如果顺序执行的话需要6秒钟:
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'testcase'])
运行结果:
如果是多线程的话,执行时间只需要4.19秒
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'testcase', '-n=2'])
运行结果:
-n 参数案例2
如果 test_login.py 里面有多个测试用例,要多线程执行这些用例应该怎么做呢?
# test_login.py
import time
class TestLogin:
def test_login(self):
time.sleep(3)
print("登录测试")
def test_username(self):
time.sleep(3)
print("用户名测试")
def test_password(self):
time.sleep(3)
print("密码测试")
def test_verification_code(self):
time.sleep(3)
print("验证码测试")
def test_protocol(self):
time.sleep(3)
print("勾选协议测试")
如果是顺序执行,需要花费15秒的时间
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'testcase/test_login.py'])
运行结果:
如果是多线程执行,只需要10秒的时间
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'testcase/test_login.py', '-n=2'])
运行结果:
–reruns num 参数案例
以 test_login.py 为例,我们给 test_password 方法设置一个错误的断言
# test_login.py
class TestLogin:
def test_login(self):
print("登录测试")
def test_username(self):
print("用户名测试")
def test_password(self):
print("密码测试")
assert 1==2
def test_verification_code(self):
print("验证码测试")
def test_protocol(self):
print("勾选协议测试")
修改 all.py 文件:
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'testcase/test_login.py', '--reruns=2'])
使用主函数模式运行结果:
可以看到对于失败的用例,它重跑了2次。
使用命令行模式运行(要先切换到 testcase 目录下)的结果:pytest -vs test_login.py --reruns 2
-k 参数 案例
假设现在只想执行 test_login.py 里面包含 user关键字的测试用例,使用命令行模式执行:
pytest -vs test_login.py -k 'user'
执行结果如下:
pytest.ini 是pytest单元测试框架的核心配置文件。
位置:一般放在项目根目录下
编码格式:必须是 ANSI,可以使用 notepad++ 修改编码格式
作用:改变 pytest 默认的行为
运行规则:不管是主函数的模式运行还是命令行模式运行,都会去读取这个配置文件
[pytest]
addopts = -vs # 命令行的参数,用空格分隔
testpaths = ./testcase # 测试用例的路径
python_files = test*.py # 模块名规则
python_classes = Test* # 类名的规则
python_functions = test # 方法名的规则
unittest: ascii的大小来绝对的执行顺序
pytest:默认从上到下顺序执行
案例:
# test_login.py
class TestLogin:
def test_04_login(self):
print("登录测试")
def test_05_username(self):
print("用户名测试")
def test_02_password(self):
print("密码测试")
assert 1==2
def test_03_verification_code(self):
print("验证码测试")
def test_01_protocol(self):
print("勾选协议测试")
# all.py
import pytest
if __name__ == '__main__':
pytest.main(['-vs', 'testcase/test_login.py'])
执行 all.py ,结果如下,可以看出是按用例顺序从上往下执行的:
如果不想按从上到下的顺序执行应该怎么做呢?
使用装饰器 @pytest.mark.run(order=num)
来指定用例的执行顺序,其中的 num 给定值为 1、2、3、4…
对于未使用装饰器的来指定的用例,还是依照从上往下的原则:
如下:
import pytest
class TestLogin:
def test_04_login(self):
print("登录测试")
@pytest.mark.run(order=1)
def test_05_username(self):
print("用户名测试")
def test_02_password(self):
print("密码测试")
assert 1==2
@pytest.mark.run(order=2)
def test_03_verification_code(self):
print("验证码测试")
@pytest.mark.run(order=3)
def test_01_protocol(self):
print("勾选协议测试")
执行结果如下,可以看到先执行添加了装饰器 @pytest.mark.run(order=num)
的用例 test_05_username -> test_03_verification_code -> test_01_protocol:
对于未添加装饰器的测试用例 test_04_login 和 test_02_password 按照从上往下的顺序执行。
smoke:冒烟用例,分布在各个模块里面
假设现在不执行全部用例,只想对部分用例做冒烟测试。只需要在需要执行的用例上加装饰器@pytest.mark.smoke
进行标记即可。这里的smoke是自己命名的,你命名为其他名字也可以。
下面对 test_login.py 的 test_02_password 和 test_logout.py 的 test_logout 做冒烟测试:
# test_login.py
import pytest
class TestLogin:
def test_04_login(self):
print("登录测试")
@pytest.mark.run(order=1)
@pytest.mark.usermanage
def test_05_username(self):
print("用户名测试")
@pytest.mark.smoke
def test_02_password(self):
print("密码测试")
# assert 1==2
@pytest.mark.run(order=2)
def test_03_verification_code(self):
print("验证码测试")
@pytest.mark.run(order=3)
def test_01_protocol(self):
print("勾选协议测试")
# test_logout.py
import time
import pytest
class TestLogout:
@pytest.mark.smoke
def test_logout(self):
time.sleep(3)
print("登出测试")
在 pytest.ini 配置文件中加入 marks = …:
[pytest]
addopts = -vs
testpaths = ./
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
smoke:冒烟用例
usermanage:用户管理模块
productmanage:商品管理模块
使用命令行模式执行:pytest -m 'smoke'
测试结果,只执行了标记为 smoke 的用例:
还是使用上面的代码,假设现在想执行冒烟测试的用例和用户管理模块的用例。在用户管理模块的用例上加上标记
@pytest.mark.usermanage
,即 test_login.py 里的 test_05_username 测试用例。
使用命令行模式执行:pytest -m 'smoke or usermanage'
测试结果,可以看出只运行了标记了 smoke 和 usermanage 的用例:
⭐无条件跳过测试用例
假设现在想跳过某个用例,不执行它,只需要在这个用例上加装饰器 @pytest.mark.skip(reason='跳过原因')
即可。
现在只对 test_login.py 内的测试用例做测试,所以需要修改第一下 pytest.ini 配置文件:
[pytest]
addopts = -vs
testpaths = ./testcase/test_login.py # 只测试 test_login.py 文件内的测试用例
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
smoke:冒烟用例
usermanage:用户管理模块
productmanage:商品管理模块
修改 test_login.py :
import pytest
class TestLogin:
def test_04_login(self):
print("登录测试")
@pytest.mark.run(order=1)
@pytest.mark.usermanage
def test_05_username(self):
print("用户名测试")
@pytest.mark.smoke
def test_02_password(self):
print("密码测试")
# assert 1==2
@pytest.mark.run(order=2)
def test_03_verification_code(self):
print("验证码测试")
@pytest.mark.run(order=3)
@pytest.mark.skip(reason='不需要勾选协议,所以跳过')
def test_01_protocol(self):
print("勾选协议测试")
使用命令行模式执行:pytest
执行结果,可以看到被标记的用例已经跳过了:
⭐有条件跳过测试用例
假设当满足某个条件就跳过某个测试用例不执行。修改 test_login.py 文件,定义一个验证码变量 code 并赋值为 666,假设当值为 666 时,验证码测试的测试用例不执行:
import pytest
class TestLogin:
code = 666
def test_04_login(self):
print("登录测试")
@pytest.mark.run(order=1)
@pytest.mark.usermanage
def test_05_username(self):
print("用户名测试")
@pytest.mark.smoke
def test_02_password(self):
print("密码测试")
# assert 1==2
@pytest.mark.run(order=2)
@pytest.mark.skipif(code=666)
def test_03_verification_code(self):
print("验证码测试")
@pytest.mark.run(order=3)
@pytest.mark.skip(reason='不需要勾选协议,所以跳过')
def test_01_protocol(self):
print("勾选协议测试")
使用命令行模式执行: pytest
执行结果,可以看到验证码测试的测试用例被跳过了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3i6frIB-1665244264349)(C:\Users\17863\AppData\Roaming\Typora\typora-user-images\image-20221004213447669.png)]
1)在项目根目录下创建目录 report 用于存放测试报告
2)修改 pytest.ini 配置文件
[pytest]
addopts = -vs --html ./report/report.html # 测试报告存放路径
testpaths = ./testcase/test_login.py
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
smoke:冒烟用例
usermanage:用户管理模块
productmanage:商品管理模块
3)使用命令行模式执行:python
4)进入report 中查看测试报告
为什么需要这些功能?
比如:web自动化执行用例之前,请问需要打开浏览器吗?用例执行后需要关闭浏览器吗?
class TestFirm:
# 这个在所有用例之前,只执行一次
def setup_class(self):
print('在每个类执行前的初始化工作:比如创建日志对象,创建数据库的连接')
# 在每个用例之前执行一次
def setup(self):
print('\n在执行测试用例之前初始化的代码:打开浏览器,加载网页')
def test_01_baidu(self):
print("打开百度")
def test_02_huawei(self):
print("打开华为")
def test_03_xiaomi(self):
print('打开小米')
def teardown(self):
print('\n在执行测试用例之后的扫尾代码:关闭浏览器')
def teardown_class(self):
print('在每个类执行后的扫尾代码:比如,销毁日志对象,销毁数据库连接')
注意:和 unittest 不一样,全是小写。
装饰器
@pytest.fixture(scope=‘’, params=‘’, autouse=‘’, ids=‘’, name=‘’)
1)scope表示的是被@pytest.fiture标记的方法的作用域。function(默认),class,module,package/session
2)params:参数化(支持,列表[],元组(), 字典列表[{}, {}, {}],字典元组({}, {}, {}))
import pytest
@pytest.fixture(scope='function', params=['郭静', '周明', '王磊'])
def my_fixture(request)
print('前置')
yield
print('后置')
return request.param
class TestMashan:
def test_01_baidu(self):
print('测试百度')
def test_02_huawei(self):
print('测试华为')
print('------------' + str(my_fixture))
params=[‘郭静’, ‘周明’, ‘王磊’] 这里 params 是参数名,有 s
request.para, 这里是属性名,没有 s
3)autouse=True:自动使用,默认False
4)ids:当使用 params 参数化时,给每一个设定一个变量名,意义不大
5)name:给表示的是被@pytest.fixture标记的方法取一个别名
当取了别名之后,那么原来的名称就用不了了
1)conftest.py 文件是单独存放在一个夹具配置文件,名称是不能修改的
2)用处可以在不用的py文件中使用同一个 fixture 函数
3)原则上 conftest.py 需要和运行的用例放到同一层,并且不需要做任何的 import 导入操作
总结:
1)setup/teardown,setup_class/teardown_class 它是作用于所有的用例和所有的类
2)@pytest.fixture() 它的作用是既可以部分也可以全部前后置
3)conftest.py 和 @pytest .fixture() 结合使用,作用于全局的前后置
assert
assert 1==2
1)下载
安装教程:https://www.cnblogs.com/XiqueBlogs/p/16178092.html
注:allure 使用前需要先安装 jdk
2)加入命令生成 json 格式的临时报告
--allure ./temp
3)生成 allure 报告
os.system('allure geneate ./temp -o ./report --clean')
需要在 pytest.ini 里面修改一下:
[pytest]
;addopts = -vs --html ./report/report.html
; 使用 allure 生成测试报告
addopts = -vs --alluredir ./temp
testpaths = ./testcase/test_baidu.py
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
smoke:冒烟用例
usermanage:用户管理模块
productmanage:商品管理模块
修改 all.py
# all.py
import os
import pytest
if __name__ == '__main__':
pytest.main()
os.system('allure generate ./temp -o ./report --clean')
执行 all.py 后,在 temp 目录下生成 json 格式的测试报告,在 report 目录下生成 html 格式的 allure 测试报告。
HTML格式测试报告:
json格式测试报告:
@pytest.mark.parametrize(args_name, args_value)
args_name:参数名
args_value:参数值(列表, 元组, 字典列表,字典元组),有多个值用例就会执行多少次
用法一
假设我们现在在做接口测试,新建一个 testapi 的包,然后在里面存放接口测试用例:
在需要传参的用例上面加装饰器,把需要传的参数填写在上面,有几个参数,这个测试用例就跑几次:
修改一下pytest.ini,让它只跑 testapi 包里面的测试用例
[pytest]
;addopts = -vs --html ./report/report.html
addopts = -vs
; 使用 allure 生成测试报告
;addopts = -vs --alluredir ./temp
testpaths = ./testapi
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
smoke:冒烟用例
usermanage:用户管理模块
productmanage:商品管理模块
在修改一下 all.py
import os
import pytest
if __name__ == '__main__':
pytest.main()
# os.system('allure generate ./temp -o ./report --clean')
执行结果,可以看到我们使用装饰器 @pytest.mark.parametrize('args', ['张三', '李四'])
传入了两个参数,所以测试用例 test_01_baidu() 执行了两次:
用法二
假设传入的是多个变量,跟 unittest 的 ddt 里面的 @unpack 解包是一样的。
# test_api.py
import pytest
class TestApi:
@pytest.mark.parametrize('name, age', [['张三', '18'],['周四', '45']])
def test_01_baidu(self, name, age):
print(name, age)
运行结果:
1.用于全局的配置文件 ini/yaml
2.用于写测试用例(接口测试用例)
yaml简介:
yaml是一种数据格式,支持注释,换行,多行字符串,裸字符串(整型,字符串)
语法规则:
1.区分大小写
2.使用缩进表示层级,不能使用tab键进行缩进,只能使用空格
3.缩进是没有数量的,只要前面是对其的就行
4.注释是 #
数据组成
1.Map对象,键值对 键:(空格)值
jack:
age: 18
country: USA
2.数组对象,用一组横线开头,如:
三 YAML 接口自动化实战
anage:用户管理模块
productmanage:商品管理模块
在修改一下 all.py
```python
import os
import pytest
if __name__ == '__main__':
pytest.main()
# os.system('allure generate ./temp -o ./report --clean')
执行结果,可以看到我们使用装饰器 @pytest.mark.parametrize('args', ['张三', '李四'])
传入了两个参数,所以测试用例 test_01_baidu() 执行了两次:
[外链图片转存中…(img-liSdV6zK-1665244264356)]
用法二
假设传入的是多个变量,跟 unittest 的 ddt 里面的 @unpack 解包是一样的。
# test_api.py
import pytest
class TestApi:
@pytest.mark.parametrize('name, age', [['张三', '18'],['周四', '45']])
def test_01_baidu(self, name, age):
print(name, age)