标签:设计 实例 tor 浏览器 failure if判断 for pre 作用
从模块级别中来看,用例加setup和teardown可以实现在测试用例之前或之后加入一些操作,但这种是整个脚本全局生效的,如果想实现以下场景:用例1需要先登录,用例2不需要登录,用例3需要先登录,那么很显然使用setup和teardown就实现不了,于是可以自动以测试用例的预置条件
fixture相对于setup和teardown来说有以下几点优势:
使用@pytest.fixture(scope="module"):module作用是当前整个py文件都能生效,参数写上函数名称即可
# 新建一个文件sdfdsf.py
# coding:utf-8
import pytest
@pytest.fixture(scope="module")
def open():
print("打开浏览器,并且打开百度首页")
def test_s1(open):
print("用例1:搜索python-1")
def test_s2(open):
print("用例2:搜索python-2")
def test_s3(open):
print("用例3:搜索python-3")
if __name__ == "__main__":
pytest.main(["-s", "sdfdsf.py"])
通过结果可以看出,三个测试用例都调用了open函数,但是只会在第一个用例之前执行一次
通过规律可以得出,如果我第一个测试用例之前不调用,那么我就在第二个测试用例调用open函数
# 新建test_demo.py
import pytest
@pytest.fixture(scope="module")
def open():
print("打开浏览器,访问至百度首页")
yield # 编写用例时,将yield写在结束操作之前就可,然后在所有用例执行完之后执行一次
print("这是teardown操作")
print("关闭浏览器")
def test_case1(open):
print("用例1")
def test_case2(open):
print("用例2")
if __name__ == "__main__":
pytest.main(["-v", "test_demo.py"])
# 结果:上面的用例都调用了open()
# 操作,在所有用例执行前执行一次open,然后运行用例,最后所有用例执行完之后执行一次yield后面的结束操作
# 注:yield在用例里面充当了teardown操作。就算用例执行报错,yield还是会正常执行不会被影响
平常写自动化用例会写一些前置的fixture操作,用例需要用到就直接传该函数的参数名称就行了,当用例很多的时候,每次都传这个参数,会比较麻烦,fixture里面有个参数autouse,默认是Fasle没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了
调用fixture三种方法:
先定义start功能,用例全部传start参数,调用该功能
# content of test_06.py
import time
import pytest
@pytest.fixture(scope="function")
def start(request):
print(‘\n-----开始执行function----‘)
def test_a(start):
print("-------用例a执行-------")
class Test_aaa():
def test_01(self, start):
print(‘-----------用例01--------------‘)
def test_02(self, start):
print(‘-----------用例02------------‘)
if __name__ == "__main__":
pytest.main(["-s", "sdfg.py"])
使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例
# content of test_07.py
import time
import pytest
@pytest.fixture(scope="function")
def start(request):
print(‘\n-----开始执行function----‘)
@pytest.mark.usefixtures("start")
def test_a():
print("-------用例a执行-------")
@pytest.mark.usefixtures("start")
class Test_aaa():
def test_01(self):
print(‘-----------用例01--------------‘)
def test_02(self):
print(‘-----------用例02------------‘)
if __name__ == "__main__":
pytest.main(["-s", "sdfg.py"])
autouse设置为True,自动调用fixture功能
# content of test_08.py
import time
import pytest
@pytest.fixture(scope="module")
def start(request):
print(‘\n-----开始执行moule----‘)
print(‘module : %s‘ % request.module.__name__)
print(‘----------启动浏览器---------‘)
yield
print("------------结束测试 end!-----------")
@pytest.fixture(scope="function", autouse=True)
def open_home(request):
print("function:%s \n--------回到首页--------" % request.function.__name__)
def test_01():
print(‘-----------用例01--------------‘)
def test_02():
print(‘-----------用例02------------‘)
if __name__ == "__main__":
pytest.main(["-s", "sdfg.py"])
如果想把登录操作放到前置操作里,也就是用到@pytest.fixture装饰器,传参就用默认的request参数
# test_02.py
# coding:utf-8
import pytest
# 测试账号数据
test_user_data = ["admin1", "admin2"]
@pytest.fixture(scope="module")
def login(request):
user = request.param
return user
@pytest.mark.parametrize("login", test_user_data, indirect=True) #添加indirect=True参数是为了把login当成一个函数去执行,而不是一个参数
def test_login(login):
‘‘‘登录用例‘‘‘
a = login
print("测试用例中login的返回值:%s" % a)
assert a != ""
if __name__ == "__main__":
pytest.main(["-s", "test_02.py"])
如果用到@pytest.fixture里面用2个参数情况,可以把多个参数用一个字典去存储,这样最终还是只传一个参数不同的参数再从字典里面取对应key值就行,如: user = request.param["user"]
# test_03.py
# coding:utf-8
import pytest
# 测试账号数据
test_user_data = [{"user": "admin1", "psw": "111111"},
{"user": "admin1", "psw": ""}]
@pytest.fixture(scope="module")
def login(request):
user = request.param["user"]
psw = request.param["psw"]
print("登录账户:%s" % user)
print("登录密码:%s" % psw)
if psw:
return True
else:
return False
# indirect=True 声明login是个函数
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
‘‘‘登录用例‘‘‘
a = login
print("测试用例中login的返回值:%s" % a)
assert a, "失败原因:密码为空"
if __name__ == "__main__":
pytest.main(["-s", "sdfdsf.py"])
用例上面可以同时放多个fixture,也就是多个前置操作,可以支持装饰器叠加,使用parametrize装饰器叠加时,用例组合是2个参数个数相乘
# test_04.py
# coding:utf-8
import pytest
# 测试账号数据
test_user = ["admin1", "admin2"]
test_psw = ["11111", "22222"]
@pytest.fixture(scope="module")
def input_user(request):
user = request.param
print("登录账户:%s" % user)
return user
@pytest.fixture(scope="module")
def input_psw(request):
psw = request.param
print("登录密码:%s" % psw)
return psw
@pytest.mark.parametrize("input_user", test_user, indirect=True)
@pytest.mark.parametrize("input_psw", test_psw, indirect=True)
def test_login(input_user, input_psw):
‘‘‘登录用例‘‘‘
a = input_user
b = input_psw
print("测试数据a-> %s, b-> %s" % (a, b))
assert b
if __name__ == "__main__":
pytest.main(["-s", "test_04.py"])
在上面的案例中,同一个py文件,多个用例调用一个登陆功能,如果有多个py文件都需要调用这个登陆功能的话,就不能把登录写到用例里面去了,此时应该有个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置
conftest.py配置需要注意一下几点:
函数修饰符的方式标记被测函数执行的顺序
安装方式:
插件名称:使用命令行 pip3 install pytest-ordering
使用方法:
标记于被测试函数,@pytest.mark.run(order=x)
order的优先级 0>较小的正数>较大的正数>无标记>较小的负数>较大的负数
根据order传入的参数来结局运行顺序
order值全为证书或权威负数时,运行顺序:值越小,优先级越高
正数和负数同时存在:正数优先级高
默认情况下,pytest默认从上往下执行,可以通过第三方插件包改变其运行顺序.
跳过测试函数的最简单方法是使用跳过装饰器标记它,可以传递一个可选的原因
@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown():
...
也可以通过调用来测试执行或设置期间强制跳过pytest.skip(reason)功能:
def test_function(): if not valid_config(): pytest.skip("unsupported configuration")
也可以使用pytest.skip(reason,allow_module_level = True)跳过整个模块级别
import pytest
if not pytest.config.getoption("--custom-flag"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
根据特定的条件,不执行标识的测试函数
方法:skipif(condition,reason=None)
参数:
condition:跳过的条件,必传参数 可以直接传True
reason:标注原因,必传参数 可以传reason="done"表示正在执行跳过
使用方法:@pytest.mark.skipif(condition,reason="xxx")
测试结果一般分为三种
当用例a失败的时候,如果用例b和用例c都是依赖于第一个用例的结果,那可以直接跳过用例b和c的测试,直接给他标记失败xfail用到的场景,登录是第一个用例,登录之后的操作b是第二个用例,登录之后操作c是第三个用例,很明显三个用例都会走到登录,如果登录都失败,那后面个用例就没有测试的必要了,并且标记为失败用例
标记测试函数为失败函数
方法:xfail(condition=None,reason=None,raises=None,run=true,strict=False)
常用参数:
condition:预期失败的条件,必传参数 如果传True表示确认设定预期失败,传False表示设定预定成功
reason:失败的原因,必传参数 reason="done",可以传任何参数值,我们设定为done
使用方法:@pytest.mark.xfail(condition,reason="xx")
pytest里面用xfail标记用例为失败的用例,可以直接跳过,实现基本思路
# content of test_05.py
# coding:utf-8
import pytest
canshu = [{"user": "amdin", "psw": "111"}]
@pytest.fixture(scope="module")
def login(request):
user = request.param["user"]
psw = request.param["psw"]
print("正在操作登录,账号:%s, 密码:%s" % (user, psw))
if psw:
return True
else:
return False
@pytest.mark.parametrize("login", canshu, indirect=True)
class Test_xx():
def test_01(self, login):
‘‘‘用例1登录‘‘‘
result = login
print("用例1:%s" % result)
assert result == True
def test_02(self, login):
result = login
print("用例2,登录结果:%s" % result)
if not result:
pytest.xfail("登录不成功, 标记为xfail")
assert 1 == 1
def test_03(self, login):
result = login
print("用例3,登录结果:%s" % result)
if not result:
pytest.xfail("登录不成功, 标记为xfail")
assert 1 == 1
if __name__ == "__main__":
pytest.main(["-s", "test_05.py"])
# content of test_05.py
# coding:utf-8
import pytest
canshu = [{"user": "amdin", "psw": ""}]
@pytest.fixture(scope="module")
def login(request):
user = request.param["user"]
psw = request.param["psw"]
print("正在操作登录,账号:%s, 密码:%s" % (user, psw))
if psw:
return True
else:
return False
@pytest.mark.parametrize("login", canshu, indirect=True)
class Test_xx():
def test_01(self, login):
‘‘‘用例1登录‘‘‘
result = login
print("用例1:%s" % result)
assert result == True
def test_02(self, login):
result = login
print("用例3,登录结果:%s" % result)
if not result:
pytest.xfail("登录不成功, 标记为xfail")
assert 1 == 1
def test_03(self, login):
result = login
print("用例3,登录结果:%s" % result)
if not result:
pytest.xfail("登录不成功, 标记为xfail")
assert 1 == 1
if __name__ == "__main__":
pytest.main(["-s", "test_05.py"])
单个参数的参数化
方法:parametrize(argnames,argvalues,indirect=False,ids=None,scope=None)
常用参数:
argnames:参数名
argvalues:参数对应值,类型必须为list ["1","2","3"]这种类型
使用方法:@pytest.mark.parametrize(argnames,argvalues) 参数值为Nge,测试方法就会运行N次
单个参数的参数化代码如下:
@pytest.mark.parametrize("keys",["1","2"]) # 第二个列表参数写一个表示执行一个参数一次,两个表示执行一个参数两次
def test_search(self,keys):
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys(keys)
多个参数的参数化代码如下:
# 第一个参数使用元祖,第二个参数使用列表嵌套元祖
@pytest.mark.parametrize(("username","password"),[("wupeng","123456"),("wupeng1","123456")])
def test_search(self,username,password):
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys(username)
self.driver.find_element_by_id("android:id/search_src_text").send_keys(password)
import pytest
import allure
class TestLogin:
datas = [
({"username": "liuxin", "password": 123456}, "success", "输入正确用户名,正确密码"),
({"username": "liuxin", "password": 123456}, "faild", "输入错误用户名,正确密码"),
({"username": "liuxin", "password": 123456}, "success", "输入正确用户名,正确密码"),
]
@pytest.mark.parametrize("username,password,value", datas)
@allure.title("{value}")
def test_login(self, username, password, value):
result = password
assert result == "success"
if __name__ == ‘__main__‘:
pytest.main(["-s", "test_pytest.py"])
在没有配置conftest.py的情况下,终端运行如下代码:
import pytest
def login(username, password):
‘‘‘登录‘‘‘
# 返回
return {"code": 0, "msg": "success!"}
# 测试数据
test_datas = [
({"username": "yoyo1", "password": "123456"}, "success!"),
({"username": "yoyo2", "password": "123456"}, "success!"),
({"username": "yoyo3", "password": "123456"}, "success!"),
]
@pytest.mark.parametrize("test_input,expected",
test_datas,
ids=[
"输入正确账号,密码,登录成功",
"输入错误账号,密码,登录失败",
"输入正确账号,密码,登录成功",
]
)
def test_login(test_input, expected):
‘‘‘测试登录用例‘‘‘
# 获取函数返回结果
result = login(test_input["username"], test_input["password"])
# 断言
assert result["msg"] == expected
def pytest_collection_modifyitems(items):
"""
测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上
:return:
"""
for item in items:
item.name = item.name.encode("utf-8").decode("unicode_escape")
print(item.nodeid)
item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
pytest可以支持自动以标记,自动以标记可以把一个web项目划分为多个模块,然后指定模块名称执行,一个大项目自动化用例时,可以划分多个模块,也可以使用标记功能,表明哪些是模块1,哪些是模块2,运行代码时指定mark名称运行就可以
以下用例,标记test_send_http()为webtest
# content of test_server.py
import pytest
@pytest.mark.webtest
def test_send_http():
pass # perform some webtest test for your app
def test_something_quick():
pass
def test_another():
pass
class TestClass:
def test_method(self):
pass
if __name__ == "__main__":
pytest.main(["-s", "test_server.py", "-m=webtest"])
只运行用webtest标记的测试,在运行的时候,加个-m参数,指定参数值webtest pytest -v -m webtest
如果不想执行标记webtest的用例,那就用"not webtest" pytest -v -m "not webtest"
如果想指定运行某个.py模块下,类里面的一个用例,如:TestClass里面test_method用例,每个test_开头(或_test结尾)的用例,函数(或方法)的名称就是用例的节点id,指定节点id运行用-v 参数,当然也可以选择运行整个class pytest -v test_server.py::TestClass
if __name__ == "__main__":
pytest.main(["-v", "test_server.py::TestClass::test_method"])
if __name__ == "__main__":
pytest.main(["-v", "test_server.py::TestClass", "test_server.py::test_send_http"])
80%的bug集中在20%的模块,越是容易出现bug的模块,bug是越改越多,当开发修复完bug后,一般是重点测上次失败的用例,那么自动化测试也是一样,所以为了节省时间,可以值测试上次失败的用例
优点:节约时间
安装插件:pip install pytest-xdist
直接加个-n参数即可,后面num参数就是并行数量,比如num设置为3 pytest -n 3
使用pytest-xdist插件也能生成html报告,完美支持pytest-html插件 pytest -n 3 --html=report.html --self-contained-html
知识盲区,后续补充...
平常在做功能测试时,经常遇到某个模块不稳定,偶然会出现一些bug,或者是特定条件需要执行多次用例的,我们需要针对某个某块用例重复执行多次
pytest-repeat是pytest的一个插件,用于重复执行单个用例或多个用例,并指定重复多次
从运行的用例结果看,是运行完其中一个测试用例一个轮回,在运行下一个测试用例一个轮回,但是希望是当前文件中的所有测试用例执行一遍后,在继续重复执行
--repeat-scope类似于pytest fixture的scope参数,--repeat-scope也可以设置参数:
如果在代码中需要标记重复多次的测试,可以使用@pytest.mark.repeat(count)装饰器
pytest断言失败后,后面的代码就不会执行了,通常一个用例会写多个断言,有时候我们希望第一个断言失败后,后面能继续断言,那么pytest-assume插件可以解决断言失败后继续断言的问题
安装 pip install pytest-assume
import pytest
@pytest.mark.parametrize((‘x‘, ‘y‘),
[(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
print("测试数据x=%s, y=%s" % (x, y))
pytest.assume(x == y)
pytest.assume(x + y > 1)
pytest.assume(x > 1)
print("测试完成!")
pytest.assume也可以使用上下文管理器去断言,需要注意的是每个with块只能有一个断言,如果一个with下有多个断言,当第一个断言失败的时候,后面的断言就不会起作用
import pytest
from pytest import assume
@pytest.mark.parametrize((‘x‘, ‘y‘),
[(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
print("测试数据x=%s, y=%s" % (x, y))
with assume: assert x == y
with assume: assert x + y == 1
with assume: assert x == 1
print("测试完成!")
pytest的配置文件通常放到测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置,在开头添加[pytest],添加剩余的内容如下:
相关代码:
[pytest]
# 添加命令行参数 添加生成报告快捷命令
addopts = -s --html=report/report.html
# 搜索哪个文件夹
testpaths = ./scripts
# 函数名
python_functions=test_*
# 文件名
python_files = test_*.py
# 类名
python_classes = Test*
基本操作:在项目根目录下创建一个log目录,log目录下创建log文件,在需要使用logging日志的时候,引入logger
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
log.py
"""
import os
import logging
import logging.handlers
__all__ = ["logger"]
# 用户配置部分 ↓
LEVEL_COLOR = {
‘DEBUG‘: ‘cyan‘,
‘INFO‘: ‘green‘,
‘WARNING‘: ‘yellow‘,
‘ERROR‘: ‘red‘,
‘CRITICAL‘: ‘red,bg_white‘,
}
STDOUT_LOG_FMT = "%(log_color)s[%(asctime)s] [%(levelname)s] [%(threadName)s] [%(filename)s:%(lineno)d] %(message)s"
STDOUT_DATE_FMT = "%Y-%m-%d %H:%M:%S"
FILE_LOG_FMT = "[%(asctime)s] [%(levelname)s] [%(threadName)s] [%(filename)s:%(lineno)d] %(message)s"
FILE_DATE_FMT = "%Y-%m-%d %H:%M:%S"
# 用户配置部分 ↑
class ColoredFormatter(logging.Formatter):
COLOR_MAP = {
"black": "30",
"red": "31",
"green": "32",
"yellow": "33",
"blue": "34",
"magenta": "35",
"cyan": "36",
"white": "37",
"bg_black": "40",
"bg_red": "41",
"bg_green": "42",
"bg_yellow": "43",
"bg_blue": "44",
"bg_magenta": "45",
"bg_cyan": "46",
"bg_white": "47",
"light_black": "1;30",
"light_red": "1;31",
"light_green": "1;32",
"light_yellow": "1;33",
"light_blue": "1;34",
"light_magenta": "1;35",
"light_cyan": "1;36",
"light_white": "1;37",
"light_bg_black": "100",
"light_bg_red": "101",
"light_bg_green": "102",
"light_bg_yellow": "103",
"light_bg_blue": "104",
"light_bg_magenta": "105",
"light_bg_cyan": "106",
"light_bg_white": "107",
}
def __init__(self, fmt, datefmt):
super(ColoredFormatter, self).__init__(fmt, datefmt)
def parse_color(self, level_name):
color_name = LEVEL_COLOR.get(level_name, "")
if not color_name:
return ""
color_value = []
color_name = color_name.split(",")
for _cn in color_name:
color_code = self.COLOR_MAP.get(_cn, "")
if color_code:
color_value.append(color_code)
return "\033[" + ";".join(color_value) + "m"
def format(self, record):
record.log_color = self.parse_color(record.levelname)
message = super(ColoredFormatter, self).format(record) + "\033[0m"
return message
def _get_logger(log_to_file=True, log_filename="default.log", log_level="DEBUG"):
_logger = logging.getLogger(__name__)
stdout_handler = logging.StreamHandler()
stdout_handler.setFormatter(
ColoredFormatter(
fmt=STDOUT_LOG_FMT,
datefmt=STDOUT_DATE_FMT,
)
)
_logger.addHandler(stdout_handler)
if log_to_file:
_tmp_path = os.path.dirname(os.path.abspath(__file__))
_tmp_path = os.path.join(_tmp_path, "../log/{}".format(log_filename))
file_handler = logging.handlers.TimedRotatingFileHandler(_tmp_path, when="midnight", backupCount=30,encoding="utf-8")
file_formatter = logging.Formatter(
fmt=FILE_LOG_FMT,
datefmt=FILE_DATE_FMT,
)
file_handler.setFormatter(file_formatter)
_logger.addHandler(file_handler)
_logger.setLevel(log_level)
return _logger
logger = _get_logger(log_to_file=True)
标签:设计 实例 tor 浏览器 failure if判断 for pre 作用
原文地址:https://www.cnblogs.com/wp950416/p/13946864.html