pytest从入门到进阶

一、初级阶段

  1. 先不写了

二、中级阶段

pytest参数化

  • 什么是参数化?
    • 通过参数的方式传递数据,从而实现数据和脚本分离。
    • 并且可以实现用例的重复生成与执行。
  • 使用场景?
    • 一般是一个完整的输入测试场景,通过用不同的输入数据,产生不同的情况
    • 如:登录场景的正向、逆向case等
  • 实现方式
    • 装饰器:@pytest.mark.parametrize
      @pytest.mark.parametrize("username,password",[["right","right"], ["wrong","wrong"]])
      def test_param(username,password):
        login(username,password)
      
  • 参数化支持的几种使用情况
    • 单参数

      • 单个参数使用时,放在列表中
        search_list = ['appium','selenium','pytest']
        
        @pytest.mark.parametrize('name',search_list)
        def test_search(name):
            assert name in search_list
        
    • 多参数

      • 将数据放在列表嵌套元组中
      • 将数据放在列表嵌套列表中
        # 数据放在元组中
        @pytest.mark.parametrize("test_input,expected",[
            ("3+5",8),("2+5",7),("7+5",12)
        ])
        def test_mark_more(test_input,expected):
            assert eval(test_input) == expected
        # 数据放在列表中
        @pytest.mark.parametrize("test_input,expected",[
            ["3+5",8],["2+5",7],["7+5",12]
        ])
        def test_mark_more(test_input,expected):
            assert eval(test_input) == expected
        
    • 用例重命名

      • 通过ids参数,将别名放在列表中
        @pytest.mark.parametrize("test_input,expected",[
            ("3+5",8),("2+5",7),("7+5",12)
        ],ids=['add_3+5=8','add_2+5=7','add_3+5=12'])
        def test_mark_more(test_input,expected):
            assert eval(test_input) == expected
        
      • 用例名输出控制台
        # 创建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")
        
    • 笛卡尔积

      • 如:两组数据
        a=[1,2,3]
        b=[a,b,c]
        对应有几种组合形势 ?
        (1,a),(1,b),(1,c)
        (2,a),(2,b),(2,c)
        (3,a),(3,b),(3,c)
      • 那在这其实就可以用笛卡尔积的方式参数化,例如下边:
        @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}")
        

Mark 标记测试用例

  • 什么是Mark标记?
    • 为用例打标记,比如在执行冒烟用例时,我就可以单独执行标记的case。
    • 也可以把一个web项目划分多个模块,然后指定模块名称执行
  • 使用场景
    • 执行冒烟用例回归
    • 指定模块回归
  • 实现方式
    • 在测试用例方法上加 @pytest.mark.标签名
  • 指令
    • -m 执行自定义标记的相关用例
      pytest -s test_mark_zi_09.py -m=webtest
      pytest -s test_mark_zi_09.py -m apptest
      pytest -s test_mark_zi_09.py -m "not ios"
      

设置用例失败自动跳过

  • 为什么使用跳过?

    • skip - 始终跳过该测试用例
    • skipif - 遇到特定情况跳过该测试用例
    • xfail - 遇到特定情况,产生一个“期望失败”输出
    • 这是 pytest 的内置标签,可以处理一些特殊的测试用例,不能成功的测试用例
  • skip使用场景

    • 调试时不想运行这个用例
    • 标记无法在某些平台上运行的测试功能
    • 在某些版本中执行,其他版本中跳过
  • 例子:当前的外部资源不可用时跳过,如果测试数据是从数据库中取到的, 连接数据库的功能如果返回结果未成功就跳过,因为执行也都报错

    • 解决 1:添加装饰器
      @pytest.mark.skip
      @pytest.mark.skipif
      
    • 解决 2:代码中添加跳过代码
      pytest.skip(reason)
      
  • xfail使用场景

  • 当预期结果为失败(fail)时,跳过

  • 使用方式

    添加装饰器@pytest.mark.xfail
    

运行用例

  • 场景
    • 运行 某个/多个 用例包
      执行包下所有的用例:pytest/py.test [包名]
      
    • 运行 某个/多个 用例模块
      执行单独一个 pytest 模块:pytest 文件名.py
      
    • 运行 某个/多个 用例类
      运行某个模块里面某个类:pytest 文件名.py::类名
      
    • 运行 某个/多个 用例方法
      运行某个模块里面某个类里面的方法:pytest 文件名.py::类名::方法名
      
  • 运行结果分析
    • 常用的:fail/error/pass
    • 特殊的结果:warning/deselect

用例调度与运行

  • 命令行参数
    • 使用缓存状态
      • –lf(–last-failed)` 只重新运行故障。
      • –ff(–failed-first)` 先运行故障然后再运行其余的测试

pytest命令行常用参数

```
  —help 
  -x   用例一旦失败(fail/error),就立刻停止执行
  --maxfail=num 用例达到
  -m  标记用例
  -k  执行包含某个关键字的测试用例
  -v 打印详细日志
  -s 打印输出日志(一般-vs一块儿使用)
  —collect-only(测试平台,pytest 自动导入功能 )
```

代码执行pytest

  • 使用 main 函数
    if __name__ == '__main__':
        # 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test.py)
        pytest.main()
        # 2、运行test_mark1.py::test_dkej模块中的某一条用例
        pytest.main(['test_mark1.py::test_dkej','-vs'])
        # 3、运行某个 标签
        pytest.main(['test_mark1.py','-vs','-m','dkej'])
    
    运行方式
      `python test_*.py `
    
  • 使用 python -m pytest 调用 pytest(jenkins 持续集成用到)

pytest异常处理

  • 常用的异常处理
    • try…except
      try:
          可能产生异常的代码块
      except [ (Error1, Error2, ... ) [as e] ]:
          处理异常的代码块1
      except [ (Error3, Error4, ... ) [as e] ]:
          处理异常的代码块2
      except  [Exception]:
          处理其它异常
      
    • pytest.raises()
      • 为什么使用它?
        • 可以捕获特定的异常
        • 获取捕获的异常的细节(异常类型,异常信息)
        • 发生异常,后面的代码将不会被执行
        def test_raise():
            with pytest.raises(ValueError, match='must be 0 or None'):
                raise ValueError("value must be 0 or None")
        
        def test_raise1():
            with pytest.raises(ValueError) as exc_info:
                raise ValueError("value must be 42")
            assert exc_info.type is ValueError
            assert exc_info.value.args[0] == "value must be 42"
        

三、高级阶段

四、进阶

五、遇到的问题