标签:设计 实例 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