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 模块化易扩展 总结链接:

原文地址:https://www.cnblogs.com/lx63blog/p/13948481.html