zoukankan      html  css  js  c++  java
  • pytest使用-文火微烹pytest

    pytest使用教程

    参考文档:pytest中文文档

    前言

      好的测试框架,核心为,每个文件,层级之间传递全局参数和函数。

    一、基本测试方法:

      1、指定目录下执行pytest,正常函数

        test_*.py文件会被默认测试

    1 def playvoice(voice):
    2     return "play "+voice
    3 
    4 def test_playvoice():
    5     assert playvoice("audio") == "play audio"

      2、指定目录下执行pytest,测试类

     1 def play_one(one):
     2     return "==="+one
     3 def play_two(two):
     4     return two+150
     5 
     6 class TestClass(object):
     7     def test_play_one(self):
     8         assert play_one("one") == "===one"
     9     def test_play_two(self):
    10         assert play_two(30) == 18

    二、pytest的功能

      1、第三方库或参数实现

        1 多线程或多进程跑用例,大大缩短执行时间。安装pytest-xdist 参数-n=2

        2 对失败用例重跑,对于一些不稳定设备测试,自动重跑是必要的。安装pytest-returnfailures 参数--reruns=1

        3 对整组自动化执行任务的约束,-x 只要一个失败即停止任务 --maxfail=2 两个失败就停止。

        4 unittest 按照ascII的大小执行,pytest按照代码安排顺序执行。

          如果想改变这个顺序可以用pytest-ordering方法。

    @pytest.mark.run(order=2)
    def test_baidu_web_channel(self):
        print("===baiduhoutaiweb_channel===")注意这个装饰器方法生肖之前,一定先安装pytest-ordering模块 
    

      2、pytest实现前后置三种方法

        1 自带setup和teardown方法

    import pytest
    
    class TestBaidu:
        # 对于所有用例前执行一次的。
        def setup_class(self,):
            print("
    ===class=== 创建日志对象,创建数据库链接,创建服务接口链接")
        def setup(self):
            print("
    ===func=== 打开浏览器,加载页面")
        def testbaiduapi_1(self,):
            print("===baiduapi_1")
        def testbaiduapi_2(self,):
            print("===baiduapi_2")
        def teardown(self):
            print("
    ===func=== 关闭浏览器")
        def teardown_class(self,):
            print("
    ===class=== 注销日志对象,数据库连接对象,接口连接对象")
    
    if __name__ == "__main__":
        pytest.main(["-vs"])
    

        执行结果:

    ============================================ test session starts ============================================
    platform win32 -- Python 3.8.5, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- c:program filespython38python.exe 
    cachedir: .pytest_cache
    rootdir: C:UsersdevevfDocumentsAItestvoiceaudio_autotestvoice_recommshiyan
    plugins: allure-pytest-2.8.19
    collected 4 items
    
    testapi/test_baiduapi.py::TestBaidu::testbaiduapi_1
    ===class=== 创建日志对象,创建数据库链接,创建服务接口链接
    
    ===func=== 打开浏览器,加载页面
    ===baiduapi_1
    PASSED
    ===func=== 关闭浏览器
    
    testapi/test_baiduapi.py::TestBaidu::testbaiduapi_2
    ===func=== 打开浏览器,加载页面
    ===baiduapi_2
    PASSED
    ===func=== 关闭浏览器
    
    ===class=== 注销日志对象,数据库连接对象,接口连接对象
    
    testapi/test_googleapi.py::TestBaidu::testgl_1 ===glapi_1
    PASSED
    testapi/test_googleapi.py::TestBaidu::testgl_2 ===glapi_2
    PASSED
    
    ============================================= 4 passed in 0.06s ============================================= 
    

        2 pytest.fixture 装置设置部分前后置方法

          无参数方法操作

    import pytest
    
    @pytest.fixture(scope="class",autouse=True)
    def setfix():
        print("===set 前置=== 打开浏览器,加载页面")
        yield
        print("===set 后置=== 关闭浏览器")
    
    class TestBaidu:
    
        def testbaiduapi_1(self,):
            print("
    ===baiduapi_1")
        def testbaiduapi_2(self, setfix):
            print("
    ===baiduapi_2")
    
    if __name__ == "__main__":
        pytest.main(["-vs"])
    

      注意:1 默认scope="function",autouse=False

          2 scope="class",autouse=True,则每个class执行

          3 scope="function",autouse=True,则测试用例是否安装装置fixture都会自动执行

        参数注释:scope 作用域范围 默认function,session/package,module,class,function

             autouse,自动执行

        参数操作

    import pytest
    
    @pytest.fixture(scope="class",params=["n1","n2","n3"])
    def setfix(request):
        print("===set 前置=== 打开浏览器,加载页面")
        yield request.param
        print("===set 后置=== 关闭浏览器")
    
    class TestBaidu:
        def testbaiduapi_1(self,):
            print("
    ===baiduapi_1")
        def testbaiduapi_2(self,setfix):
            print("
    ===baiduapi_2===", setfix)
    

        注意:前后置方法 入参名字一定是request

           yield request.param返回一个元素

           yield前后为前后置

        name参数可以重命名装饰器函数

    @pytest.fixture(scope="class",params=["n1","n2","n3"],name="zhuang")
    def setfix(request):
        print("===set 前置=== 打开浏览器,加载页面")
        yield request.param
        print("===set 后置=== 关闭浏览器")
    

      3 基于conftest.py和fixture的全局前置方法

        核心思想,针对项目的前置方法。

        项目全局登录和模块相同全局操作

        特点:1 单独存在,名字不可修改,fixture配置文件,不用引用,可以跨文件执行。

            2 每一级都可配置文件,优先使用本级的配置文件中前置方法

            3 多个前后置方法,按照排列顺序执行

        代码为 模块级conftest.py

    import pytest
    @pytest.fixture(scope="function",params=["baidu key"])
    def baidusetfix(request):
        print("===set 前置=== 打开浏览器,加载页面")
        yield request.param
        print("===set 后置=== 关闭浏览器")
    

      代码为项目级conftest.py

    import pytest
    @pytest.fixture(scope="function",params=["n1","n2"])
    def setfix(request):
        print("===set 前置=== 打开浏览器,加载页面")
        yield request.param
        print("===set 后置=== 关闭浏览器")
    

      模块内测试用例调用顺序为

    class TestBaidu:
    
        def testbaiduapi_1(self,setfix,baidusetfix):
            print("
    ===baiduapi_1",setfix,baidusetfix)
    

      3、跳过用例

        无条件

    import pytest
    class TestBaidu:
        def testbaiduapi_1(self,):
            print("
    ===baiduapi_1===")
        @pytest.mark.skip(reason="环境维护")
        def testbaiduapi_2(self,):
            print("
    ===baiduapi_2===")
        def testbaiduapi_3(self,):
            print("
    ===baiduapi_3===")
    

        结果api2被跳过

    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_1
    ===baiduapi_1===
    PASSED
    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_2 SKIPPED
    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_3
    ===baiduapi_3===
    PASSED
    
    ======================================= 2 passed, 1 skipped in 0.06s ========================================
    

      

        有条件 skipif 第一位:条件,第二位原因

    import pytest
    class TestBaidu:
        conf=False
        def testbaiduapi_1(self,):
            print("
    ===baiduapi_1===")
        @pytest.mark.skipif(conf==True,reason="环境")
        def testbaiduapi_2(self,):
            print("
    ===baiduapi_2===")
        def testbaiduapi_3(self,):
            print("
    ===baiduapi_3===")
    

        结果因为条件判断不相等 所以不跳过

    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_1
    ===baiduapi_1===
    PASSED
    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_2
    ===baiduapi_2===
    PASSED
    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_3
    ===baiduapi_3===
    PASSED
    
    ============================================= 3 passed in 0.13s ============================================= 
    

      4、数据驱动的标准写法

        pytest.mark.parametrize()

        字符串变量 + params

        “字符串,字符串”+params 解压缩

        注意params入参数据结构为列表,元组,列表字典,元组字典。当然可以列表中带列表

    import pytest
    class TestBaidu:
        def testbaiduapi_1(self,):
            print("
    ===baiduapi_1===")
        @pytest.mark.smoke
        @pytest.mark.parametrize("code",["utf","gbk","uni"])
        def testbaiduapi_2(self,code):
            print("
    ===baiduapi_2===")
            print("encode===",code)
        @pytest.mark.user
        @pytest.mark.parametrize("api,url",[["baidu","www.baidu"],["gl","www.google"]])
        def testbaiduapi_3(self,api,url):
            print("
    ===baiduapi_3===")
            print("===",api,url)
    

      

    
    

    三、pytest 操作

      1、pytest默认规则

        1 文件/模块以test_, _test 起始或结尾

        2 类 以Test开始,不能带有init方法

        3 函数或方法以test开头。

      

      2、执行方法

        1 pytest.main()

          可以添加入参,详见本章第三小节

          调用格式如下:

    import pytest
    if __name__=="__main__": pytest.main(["-s"])

          作用范围,与之同级的符合pytest运行规则的所有文件,目录内文件。

            

              这里test_ail.py同级的cases_inter目录下的test_baidu.py内函数被执行了。

               因为自己文件名也是test_ail.py 所以图中右侧部分的test_print类中的方法也会被执行。

              也可以运行主函数时,函数所在文件命名为ail.py则其中的函数就不会执行了。

              我们也尝试了在cases_inter/test_baidu.py中加入pytest.main(),执行后,test_baidu外层的文件是不执行的,如test_ail.py文件没有执行。

         2 对应的命令行也可以执行

          在terminal中执行

            

         3 配置文件pytest.ini执行

          1 位置 测试项目根目录下

          2 要求:使用notepad++ 修改编码为ANSI格式,其中不能有中文

          3 作用可以修改pytest默认规则

          4 规则,不管是主函数方法还是terminal方法都会先读取这个文件

            pytest --help指令可以查看pytest.ini的设置选项

            显示如下

    [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
    
      markers (linelist)       markers for test functions
      empty_parameter_set_mark (string) default marker for empty parametersets
      norecursedirs (args)     directory patterns to avoid for recursion
      testpaths (args)         directories to search for tests when no files or dire
    
      console_output_style (string) console output: classic or with additional progr
    
      usefixtures (args)       list of default fixtures to be used with this project
    
      python_files (args)      glob-style file patterns for Python test module disco
    
      python_classes (args)    prefixes or glob names for Python test class discover
    
      python_functions (args)  prefixes or glob names for Python test function and m
    
      xfail_strict (bool)      default for the strict parameter of 
      addopts (args)           extra command line options
      minversion (string)      minimally required pytest version
    

        简约版

    [pytest]
    addopts = -vs
    testpaths = ./cases_inter
    python_files = unit_*.py
    python_classes = Test*
    python_functions = test
    

      只执行main或者pytest,不用加参数和指定目录,即可完成配置测试任务。

      3、参数

        注意参数应该放在列表中写,pytest.main(["-s","test_baidu.py"])

        -s 是显示测试中的print打印

        -v 是显示测试用例的详细信息

        用双::来标定类名,文件/模块名,函数名(用例)。

          

            目录和文件之间用路径符号分割,注意/ 和

            模块名和 用例方法名用 ::双::分割

            文件中还是可以不在类中定义函数。如test_bd测试用例

              

    def test_bd():
        print("===baidu inter")
    
    class TestBaidu:
        def test_baidu(self):
            print("baidu")
    

      

         -n  支持多线程,也有支持多进程的库,参见pytest-parallel

          前提条件,安装pytest-xdist,pip install pytest-xdist

          参数输入格式,terminal中 

          pytest -vs cases_inter est_baidu.py -n=2

          pytest.main中 

    if __name__ == "__main__":
    pytest.main(["-vs","-n=2",r"cases_inter est_baidu.py"])

        -reruns num
        作用为失败用例重跑。
        安装方法:
    pip install pytest-rerunfailures -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
    pip install pytest-rerunfailures直接安装可能显示安装内容不全的信息。

        操作方法:
          pytest.main 参数方法:
    if __name__=="__main__":
    pytest.main(["-vs","--reruns=1"])
          结果:

    =========================== short test summary info ===========================
    FAILED test_baidu.py::TestBaidu::test_baidu - assert 1 == 2
    ==================== 1 failed, 1 passed, 1 rerun in 0.36s =====================

    注意

    --reruns=1  reruns前是两个--, 1 表示失败的用例重新跑一次

          terminal 命令行
          pytest -vs cases_inter est_baidu.py --reruns=1

    ======================================================= short test summary info 
    FAILED cases_inter/test_baidu.py::TestBaidu::test_baidu - assert 1 == 2
    ================================================= 1 failed, 1 passed, 1 rerun in 0.21s 

     

        -x 参数    只要一个用例即可停止,这个针对冒烟测试比较好用。 --maxfail 只要两个失败就停止。

        -k 根据测试用例名称包含指定字符串的执行

    if __name__=="__main__":
        pytest.main(["-vs","--reruns=1","-k=web"])
    

        --html ./report/report.html 生成报告。不常用,常用alluer框架生成。

          1 安装pytest-html

          2 --html ./report/report.html,也可以再ini配置

        -m 按照标记执行,

          背景:冒烟测试,用例分布在各个模块中,需要从中挑选

             任务需要按照模块执行,或按照api执行,就需要为指定用例标记

          标记步骤,1 ini配置

    [pytest]
    addopts = -vs
    markers = 
        smoke: 冒烟测试
        user: 用户管理
    

          2 测试脚本添加装饰器

    import pytest
    class TestBaidu:
        def testbaiduapi_1(self,):
            print("
    ===baiduapi_1===")
        @pytest.mark.smoke
        def testbaiduapi_2(self,):
            print("
    ===baiduapi_2===")
            assert 1==2
        @pytest.mark.user
        def testbaiduapi_3(self,):
            print("
    ===baiduapi_3===")
    

          3 -m 命令行执行 注意支持and or 如:“smoke or user” or写在字符串中

    pytest -m "smoke or user" .	estapiaidu
    

      

          结果

    collected 3 items / 1 deselected / 2 selected
    
    testapiaidu	est_baiduapi.py::TestBaidu::testbaiduapi_2
    ===baiduapi_2===
    PASSED
    ===baiduapi_3===
    PASSED
    
    ============================================= warnings summary ============================================== 
    testapiaidu	est_baiduapi.py:5
      C:UsersdevevfDocumentsAItestvoiceaudio_autotestvoice_recommshiyan	estapiaidu	est_baiduapi.py:5: 
    PytestUnknownMarkWarning: Unknown pytest.mark.run - is this a typo?  You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html
        @pytest.mark.run(reruns=2)
    
    -- Docs: https://docs.pytest.org/en/stable/warnings.html
    ================================ 2 passed, 1 deselected, 1 warning in 0.07s ================================= 
    

      

    问题:

    1 参考

    @pytest.fixture(scope='class')
    def gfixtures(request, logger, adb, carsim, uia):
       # https://stackoverflow.com/questions/26405380/how-do-i-correctly-setup-and-teardown-my-pytest-class-with-tests
       # https://computableverse.com/blog/pytest-sharing-class-fixtures
       # https://stackoverflow.com/questions/53800448/many-pytest-fixtures-vs-one-large-container-fixture
       # https://stackoverflow.com/questions/40139956/access-testcase-name-in-pytest-fixture
       # http://hackebrot.github.io/pytest-tricks/fixtures_as_class_attributes/
       request.cls.logger = logger
       request.cls.adb = adb
       request.cls.uia = uia
       request.cls.carsim = carsim
       # request.cls.uiselector = uiselector  # f() takes 1 positional argument but 2 were given
       # from pytestlib.fixtures import fixtures
       # request.cls.fixtures = fixtures
       yield

    2

    理解fixture 注册流程 与 request 范围:基本不能跨文件 只有注册了fixture的函数可以跨文件

     >>> 实验结果

      核心目标 文件之间的传递参数和实例化对象

      1 conftest中 

    @pytest.fixture(scope="module")
    def ut_ini(request):
        request.cls.ut = UtilTool()
      2 测试脚本中可以
      def fun (self,request,ut_ini)
      
            print("  request ==== ",request.__dict__)
            ut_ini
            print(" ===> cls", request.cls.__dict__)
     
      结果:在conftest中实例化的UtilTool没有传过来。
            print("  request ==== ",request.__dict__)
            ut_ini
            print(" ===> cls", request.cls.__dict__)
     
      2 conftest中定义的def怎么在测试脚本中调用时传入参数???
     
        如下两个应用是否可以解决传参问题
        @pytest.mark.parametrize("login",test_users,indirect=True) indirect解决parametrize中传入函数问题
          indirect间接使用反射特性"login"为函数
     
       传入两个参数 
    test_users=[{"user":"admin","pwd":"123456"},{"user":"test","pwd":""}]
    @pytest.fixture(scope="module")
    def login(request):
        user=request.param["user"]
        pwd=request.param["pwd"]
        print("登录账户:%s" % user)
        print("登录密码:{}".format(pwd))
        return pwd
    

      

          
     
     
     

    参考pytest 中文文档第五章

    第五章 fixture 模块化易扩展 总结链接:

  • 相关阅读:
    swift2.2当中的inout参数的使用
    Swift的7大误区
    Swift 设计指南之 编程规范
    我为什么用 SQLite 和 FMDB 而不用 Core Data
    ios学习笔记——代理设计模式
    ios学习笔记——UIImagePickerController
    ios学习笔记——保存图片到相册
    KVC中setValuesForKeysWithDictionary: (转载)
    ios学习笔记——GCD简介
    ios学习笔记——操作队列NSOperation的基本操作
  • 原文地址:https://www.cnblogs.com/lx63blog/p/13948481.html
Copyright © 2011-2022 走看看