pytest测试框架01--Loxida

pytest测试框架01

1、pytest测试用例及框架结构

(1)用例结构

def test_XXX(self):
        #测试步骤1
        #测试步骤2
        #断言  实际结果 对比 预期结果
    assert ActualResult == ExpectedResult   # 断言

(2)框架结构setup/teardown

①setup_module/teardown_module:全局模块级 
②setup_class/teardown_class:类级,只在类中前后运行一次
③setup_function/teardown_function:函数级,在类外
④setup_method/teardown_method:方法级,类中的每个方法执行前后
⑤setup/teardown:在类中,运行在调用方法的前后
#模块级别,只被调用一次
    def setup_module():
        print("资源准备:setup module")


    def teardown_module():
        print("资源销毁:teardown module")


    def test_case1():
        print("case1")


    def test_case2():
        print("case2")


    #方法级别
    def setup_function():
        print("资源准备:setup function")


    def teardown_function():
        print("资源销毁:teardown function")


    #类级别
    class TestDemo:
        #执行类 前后分别执行setup_class ,teardown_class
        def setup_class(self):
            print("TestDemo setup_class")

        def teardown_class(self):
            print("TestDemo teardown_class")

        #每个类里面的方法,前后执行setup ,teardown
        def setup(self):
            print("TestDemo setup")

        def teardown(self):
            print("TestDemo teardown")

        def test_demo1(self):
            print("test demo1")

        def test_demo2(self):
            print("test demo2")

2、pytest参数化用例

(1)参数化

①通过参数的方式传递数据,从而实现数据和脚本分离。
②并且可以实现用例的重复生成与执行。

(2)参数化实现方法

装饰器:@pytest.mark.parametrize

1)单参数,可以将数据放在列表中
#单参数,参数化实现用例的动态生成
    search_list01 = ["appium", "selenium", "pytest"]


    @pytest.mark.parametrize("search_key", 
    ["appium", "selenium", "pytest", "", "requests", "abc"])
    def test_search01(search_key):
        assert search_key in search_list01   # 断言
2)多参数情况:

①将数据放在列表嵌套元组中
②将数据放在列表嵌套列表中
#多参数,参数化实现用例的动态生成
    #列表嵌套元组
    @pytest.mark.parametrize("username, password", 
    [("right username", "right password"), 
    ("wrong username", "wrong password")])
    def test_login(username, password):
        print(f"登录的用户名:{username},密码:{password}")


    #列表嵌套列表
    @pytest.mark.parametrize("username, password", [
    ["right username", "right password"], 
    ["wrong username", "wrong password"]])
    def test_login(username, password):
        print(f"登录的用户名:{username},密码:{password}")

3)用例重命名-添加 ids 参数

①通过ids参数,将别名放在列表中
#用例重命名-添加ids 参数,为用例起个别名,ids列表参数的个数要与参数值的个数一致
    @pytest.mark.parametrize("username, password",
                             [["right username", "right password"],
                              ["wrong username", "wrong password"],
                              ["", "password"]],
                             ids=["right username and password", 
                             "wrong username and password", 
                             "username is null"])
    def test_login(username, password):
        print(f"登录的用户名:{username},密码:{password}")
②用例重命名-添加 ids 参数(中文)
#第一步,创建conftest.py 文件 ,将下面内容添加进去,运行脚本
    def pytest_collection_modifyitems(items):
        """
        测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控         制台上
        """
        for i in items:
            i.name=i.name.encode("utf-8").decode("unicode_escape")
            i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")

    #用例重命名- ids 支持中文
    @pytest.mark.parametrize("username, password",
                             [["right username", "right password"],
                              ["wrong username", "wrong password"],
                              ["", "password"]],
                             ids=["正确的用户名和密码", 
                             "错误的用户名和密码", 
                             "用户名为空"])
    def test_login(username, password):
    print(f"登录的用户名:{username},密码:{password}")
4)笛卡尔积
#笛卡尔积,装饰器优先选择离方法最近的,由近及远
    @pytest.mark.parametrize("b", ["a", "b", "c"])
    @pytest.mark.parametrize("a", [1, 2, 3])
    def test_param1(a, b):
        print(f"笛卡积形式的参数化中 a={a} , b={b}")

3、pytest标记测试用例

1)场景:只执行符合要求的某一部分用例 可以把一个web项目划分多个模块,然后指定模块名称执行。
        
2)解决: 在测试用例方法上加 @pytest.mark.标签名
        
3)执行: -m 执行自定义标记的相关用例
import pytest
    def double(a):
        return a * 2

    #测试数据,整型
    @pytest.mark.int
    def test_double_int():
        print("test double int")
        assert 2 == double(1)


    #测试数据,负数
    @pytest.mark.minus
    def test_double_minus():
        print("test double minus")
        assert -2 == double(-1)


    #测试数据,浮点型
    @pytest.mark.float
    def test_double1_float():
        print("test double float")
        assert 0.2 == double(0.1)


    @pytest.mark.float
    def test_double2_minus():
        print("test double float")
        assert -0.2 == double(-0.1)


    @pytest.mark.zero
    def test_double_0():
        print("test double zero")
        assert 0 == double(0)


    @pytest.mark.bignum
    def test_double_bignum():
        print("test double bignum")
        assert 200 == double(100)


    #测试数据,字符串
    @pytest.mark.str
    def test_double_str1():
        print("test double str")
        assert 'aa' == double('a')


    @pytest.mark.str
    def test_double_str2():
        print("test double str")
        assert 'a$a$' == double('a$')

4)Terminal执行命令行,只执行字符串str,-vs打印日志:
pytest_test % pytest test_003.py -vs -m "str"
5)出现warnings警告提示,不影响运行,是因为给测试用例加上了标签的名字,pytest无法识别这些标签名字,pytest有内置的标签

解决:新建一个pytest.ini文件,将新标签名字添加进去,不要写在一行,会无法识别,换行也不要放在顶头,会认为一个key
[pytest]
    markers = str
              bignum
              minus
              int
              zero
              float

4、pytest设置跳过Skip、预期失败用例xFail

1)这是 pytest 的内置标签,可以处理一些特殊的测试用例,不能成功的测试用例

2)skip - 始终跳过该测试用例

3)skipif - 遇到特定情况跳过该测试用例

4)xfail - 遇到特定情况,产生一个“期望失败”输出

    ①添加装饰器
@pytest.mark.skip
    @pytest.mark.skipif

    import pytest
    #添加装饰器
    @pytest.mark.skip
    def test_aaa():
        print("暂未开发")
        assert True

    #添加跳过原因
    @pytest.mark.skip(reason="暂未实现")
    def test_b():
        print("暂未开发")
        assert True
②代码中添加跳过代码:pytest.skip(reason)
#代码中添加跳过代码,pytest.skip(reason=)
    def check_login():
        return True


    def test_function():
        print("start")
        # 如果未登录,则跳过后续步骤
        if not check_login():
            pytest.skip(reason="unsupported configuration")
        print("end")
③预期结果为 fail ,标记用例为 fail,通过为XPASS,不通过为XFAIL。用法:添加装饰器@pytest.mark.xfail
#预期结果为 fail ,标记用例为 fail
    @pytest.mark.xfail  # 标记后依然会被执行,起到提示左右
    def test_a():
        print("xfail 方法执行")
        assert 2 == 2

    @pytest.mark.xfail
    def test_b():
        print("xfail 方法执行")
        assert 1 == 2

    #添加原因说明
    xfail = pytest.mark.xfail
    @xfail(reason="bug")
    def test_b():
        print("xfail 方法执行")
        assert 2 == 2

5、pytest测试用例调度与运行

1)执行包下所有的用例:pytest/py.test [包名]

2)执行单独一个 pytest 模块:pytest 文件名.py

3)运行某个模块里面某个类:pytest 文件名.py::类名

4)运行某个模块里面某个类里面的方法:pytest 文件名.py::类名::方法名

5)命令行参数-使用缓存状态

    ①--lf(--last-failed) 只重新运行故障。
    ②--ff(--failed-first) 先运行故障然后再运行其余的测试

6、pytest命令行常用参数

①—help 
②-x   用例一旦失败(fail/error),就立刻停止执行
③--maxfail=num 失败用例达到num,就停止执行
④-m  标记用例
⑤-k  执行包含某个关键字的测试用例,如-k “str”
⑥-v 打印详细日志
⑦-s 打印输出日志(一般-vs一块儿使用)
⑧—collect-only(测试平台,pytest 自动导入功能 )只收集用例,不运行

7、python执行pytest

(1)使用 main 函数

python的机制,__name__等于__main__;print(__name__),输出结果为__main__
if __name__ == '__main__':
        # 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test.              py)
        pytest.main()
        # 2、运行test_mark1.py::test_dkej模块中的某一条用例
        pytest.main(['test_002.py::test_case1', '-vs'])
        # 3、运行某个 标签
        pytest.main(['test_003.py', '-vs', '-m', 'str'])

(2)使用 python -m pytest 调用 pytest(jenkins 持续集成用到)

8、pytest异常处理

(1)异常处理方法 try …except

try:
        a = int(input("输入被除数:"))
        b = int(input("输入除数:"))
        c = a / b
        print("您输入的两个数相除的结果是:", c)
    except(ValueError, ArithmeticError):
        print("程序发生了数字格式异常、算术异常之一")
    except:
        print("未知异常")
    print("程序继续运行")

(2)异常处理方法 pytest.raise()

①可以捕获特定的异常
②获取捕获的异常的细节(异常类型,异常信息)
③发生异常,后面的代码将不会被执行
 import pytest
    def test_raise():
        with pytest.raises(ValueError, match='must be 0 or None'):
            raise ValueError("value must be 0 or None")

    def test_raise1():
        # ValueError为预期异常
        with pytest.raises(ValueError) as exc_info:   
            # 抛出的异常ZeroDivisionError
            raise ZeroDivisionError("value must be 42")  
        assert exc_info.type is ValueError
        assert exc_info.value.args[0] == "value must be 42"