pytest

pytest和unittest区别:

1、表达用例:
unittest: 定义一个测试类,继承unittest.TestCase
pytest: 类/函数

2、断言的表达:
unittest: self.assertXXXX()
pytest: assert 表达式(结果为True-断言成功,否则-断言失败!) 逻辑/成员/比较/函数返回值

3、收集用例:
unittest: TestLoader类+TestSuite类,discover收集用例
pytest: 自动收集用例

4、fixture: 前置后置。
unittest: setup&teardown setupClass&teardownClass --- 固定的名称
pytest: function(用例) -> class(测试类) -> Module(.py) -> Session(会话)

5、插件:
unittest: 无
pytest: 700+
http://plugincompat.herokuapp.com/
html插件
allure报告
重运行
1、报告
2、用例收集
3、执行用例
4、断言
5、数据驱动
6、重新运行失败的用例
7、筛选
8、前置后置的处理。
9、加载测试用例

pytest收集测试用例的规则:
1、默认从当前目录中搜集测试用例,即在哪个目录下运行pytest命令,则从哪个目录当中搜索;
2、搜索规则符合以下标准的文件才会去确认是否有用例:
1)符合命名规则 test_*.py 或者 *_test.py 的文件 .py文件
2)以test_开头的函数名;
3)以Test开头的测试类(没有__init__函数)当中,以test_开头的函数

执行顺序:
1.文件名称顺序 -ACSII排序
2.哪个文件先识别哪个用例先执行
3.文件内部:代码的先后顺序




对测试用例打标签。在运行测试用例的时候,可根据标签名来过滤要运行的用例。
使用方法:
1、注册标签名
2、在测试用例/测试类前面加上:@pytest.mark.标记名

注册方式:
1、创建pytest.ini文件,在文件中按如下形式添加标签名:
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
serial
demo
smoke
注:冒号之后是可选的描述信息

2、运行的时候,只运行打了标记的用例。
pytest.main(["-m","标签名","-s","-v"])
unittest.main()
打标记范围:测试用例、测试类、模块文件

使用方法-1:
在测试用例/测试类前面加上:@pytest.mark.标记名,如:@pytest.mark.slow

可在一个用例上打多个标签。多次使用@pytest.mark.标签名即可。
示例:
@pytest.mark.smoke
@pytest.mark.demo
def test_demo():
print(“我是示例啦!!!”)


使用方法-2:
在测试类里,使用以下申明(测试类下,所有用例都被打上该标签):
class TestClass(object):
pytestmark = pytest.mark.已注册标签名
pytestmark = [pytest.mark.标签1, pytest.mark.标签2] # 多标签模式


在 模块文件里,同理(py文件下,所有测试函数和测试类里的测试函数,都有该标签):
import pytest
pytestmark = pytest.mark.webtest
pytestmark = [pytest.mark.标签1, pytest.mark.标签2] # 多标签模式

 



事例:

pytest.ini

[pytest]
markers =
    demo: just for demo show
    nmb
    smoke

test_demo.py

import pytest

# pytestmark = pytest.mark.nmb

def aaa():
    pass


# @pytest.mark.nmb
class TestAA:

    # pytestmark = pytest.mark.nmb

    def test_bbb(self):
        print("bbbb")
        self.bbb()
        assert "hello" == "World!"

    @pytest.mark.demo
    def test_hello_pytest(self):
        print("hello,pytest!!!")

    def bbb(self):
        print("*************88")



@pytest.mark.demo
def test_aaa():
    print("aaaaaaa")
    assert True

test_nmb.py

import pytest


@pytest.mark.demo
def test_nmb_py25():
    print("python25!")

run.py

import pytest

# pytest.main(["-s","-m","demo","-v"])
pytest.main()  # 收集到的用例 ,全部运行。
    
 定义:1、怎么知道函数它是一个前置&后置?
           在函数前面 :@pytest.fixture
           @pytest.fixture
           def init():
              pass

           2、怎么区分前置和后置? yield -- 前后置的分隔分
           @pytest.fixture
           def init():
              driver = webdriver.Chrome()
              driver.get("http://www.baidu.com")
              yield
              driver.quit()

           3、作用域是什么?测试函数/类/模块/会话?  @pytest.fixture(scope=?) 
        四个级别的前置后置:
function: 默认值。
-- unittest当中setup和teardown。夹了测试用例。 默认是function的scope就是测试用例
class:测试类。 --- unittest当中setupClass和teardownClass. 面包里夹的是测试类,每个class的test只运行一次 module: 模块。.py ---- 整个py文件。 厨房里找到的鸡蛋 -- 2片面包
session:会话。
--- 夹是你收集到的所有测试用例。 家里所有的鸡蛋 -- 2片面包 接口:前置: 连接数据库? 后置:关闭连接 4、返回前置当中的变量:yield 返回值1 返回值2 调用: 4、测试用例当中,如何调用这个前置后置? 在测试用例/类的前面: @pytest.mark.usefixture("定义的fixture名称") 执行了init对应的前置动作 5、接收返回值的方式: 将fixture的函数名称,作为用例的参数 用例的参数 = 返回值 测试用例的参数:1、数据驱动;2、fixture的返回值。 如果你要使用fixture的返回值,那一定要传参。可以不用@pytest.mark.usefixtures 如果fixture没有返回值,测试要使用,必须申明:@pytest.mark.usefixtures

# 3、如何应用在测试用例上? - 测试用例当中,主动引用需要的fixture。
1、通过装饰器直接使用:
用例不需要使用fixture的返回值:
@pytest.mark.usefixture("fixture的函数名称")

用例需要使用fixture的返回值:
第一步:测试用例/类 @pytest.mark.usefixture("fixture的函数名称")
第二步:将fixture的函数名称 作为 测试用例的 参数。 fixture函数名称 = fixture函数的返回值。

2、可以使用多个前置后置。但是不能冲突 。比如:打开2次浏览器。


3、session级别的会话 - autouse可以设置为True

4、pytest用例的执行顺序:文件名称的顺序 - 测试用例的代码顺序

# 我有许多的不同的前置 后置,怎么办?
  1)层级的conftest.py
  2)有些前置后置,只有自己的测试用例要用。直接在测试用例文件中定义fixture.

共享:conftest.py ---名称固定。专 1)专门用来存放fixture. 2)作用域(哪些范围内的用例都可以使用):conftest.py所在的文件夹内的用例都可以使用 3)用例文件当中不需要引入,直接调用fixture名称就可以了。 4)可以在不同的层级,创建conftest.py

示例:
@pytest.fixture #默认scope为function
def myfixture():
driver = webdriver.Chrome() #测试用例执行之前,执行的准备工作
yield driver #将driver作为返回值。在测试用例中需要使用driver
driver.close() #测试用例执行完成之后,要执行的清理操作
driver.quit() #测试用例执行完成之后,要执行的清理操作

事例:

test_web.py文件

import pytest
from selenium import webdriver




@pytest.fixture(scope="class")#修改为class类
def mycc():
    print("======我是类级别的前置!!,开始======")
    yield
    print("======我是类级别的后置,再见======")




# 测试用例
# def test_baidu():
#     print("1111111111111111111111111")
#
# @pytest.mark.usefixtures("init")
# def test_taobao():
#     print("taobao.......")


# @pytest.mark.usefixtures("init")  # 夹的是测试函数 #
@pytest.mark.usefixtures("mycc")  # 夹的是测试类
class TestAA:


    def test_aa(self):
        print("***********  test_aa  ***********")

    # @pytest.mark.usefixtures("init")
    def test_bb(self):
        print("***********test_bb***********")

    def test_baidu(self,init):  # init = driver对象 # init = (driver,True)
        print("baidu.......")
        init.find_element_by_id("kw").send_keys("nmb")

    # 假设这个类下面,有10个用例,只有1个用例的前置后置不太一样,你会怎么处理?
    # 拎取出来当成函数。


conftest.py文件

import pytest from selenium import webdriver # 定义fixture @pytest.fixture() # pytest就能识别它为一个前置后置 def init(): # 前置 print("****我是函数别的前置!!,开始****") driver = webdriver.Chrome() driver.get("http://www.baidu.com") # yield driver,True # 返回driver对象,可以返回多个对象 yield driver # 后置 print("****我是函数别的后置!!,结束****") driver.quit() #conftest.py 共享的范围:它爸爸下的所有测试用例。 #定义公共的fixture,多个测试类中都可以调用。 #pytest提供了conftest.py文件,可以将fixture定义在此文件中。 #运行测试用例时,不需要去导入这个文件,会自动去查找conftest.py文件,然后去找到对应的fixture。




输出:

======我是类级别的前置!!,开始======
PASSED [ 33%]*********** test_aa ***********
PASSED [ 66%]***********test_bb***********
****我是函数别的前置!!,开始****
PASSED [100%]baidu.......
****我是函数别的后置!!,结束****
======我是类级别的后置,再见======

 

pytest的参数化:
    在测试用例的前面加上:
    @pytest.mark.parametrize("参数名",列表数据)
    参数名:用来接收每一项数据,并作为测试用例的参数。
    列表数据:一组测试数据。

    @pytest.mark.parametrize("参数1,参数2",[(数据1,数据2),(数据1,数据2)])
    示例:
       @pytest.mark.parametrize("a,b,c",[(1,3,4),(10,35,45),(22.22,22.22,44.44)])
       def test_add(a,b,c):
            res = a + b
            assert res == c


    组合参数化:多组参数,依次组合。
    使用多个@pytest.mark.parametrize
    
    示例:用例有4个:0,2/0,3/1,2/1,3  迪卡尔积
    @pytest.mark.parametrize("x", [0, 1])
    @pytest.mark.parametrize("y", [2, 3])
    def test_foo(x, y):
        pass


pytest插件,安装好了,有的会带来额外的运行参数。
    重运行机制 - 针对的失败的用例/ 重运行几次?
       Pytest提供了失败重试机制:
        插件名称:rerunfailures
        安装方法:pip install pytest-rerunfailures
        重试方式:用例一旦失败了,马上重运行用例。
    
        使用方式:
        命令行参数形式:
        命令:pytest --reruns 重试次数
        比如:pytest --reruns 2 表示:运行失败的用例可以重新运行2次。
    
        命令:pytest --reruns 重试次数 --reruns-delay 次数之间的延时设置(单位:秒)
        Pytest --reruns 2 --reruns-delay 5
        表示失败的用例可以重新运行2次。第一次和第二次的间隔时间为5秒钟。



html的测试报告:
安装:pip install pytest-html

命令:pytest --html=相对路径
conftest.py

# 测试用例级别

import pytest
from selenium import webdriver
from web_basepage.TestDatas import Common_Datas as CD
from web_basepage.PageObjects.login_page import LoginPage
import logging
from web_basepage.Common import logger

@pytest.fixture
def init_driver():
    """
    前置:打开浏览器,访问系统网址
    后置:退出浏览器。
    """
    logging.info("***** conftest.py共享的 init_driver 的前置  *****")
    driver = webdriver.Chrome()
    driver.get(CD.login_url)
    yield driver
    driver.quit()
    logging.info("***** conftest.py共享的 init_driver 的后置  *****")

"""
前置:打开浏览器,访问系统网址 + 登陆系统
后置:退出浏览器。
"""
@pytest.fixture
def init_login(init_driver): # init_driver代表的是它的返回值:driver
    LoginPage(init_driver).login(CD.user, CD.passwd)
    yield init_driver  # 返回driver对象。
    print("1111111111111")
"""
init_driver的前置
init_login的前置
init_login的后置
init_driver的后置

假设:init_driver是class级,init_login是function级别?
      可以"继承"。
      init_driver是function级,init_login是function级别?
      可以互相调用。
      init_driver是function级,init_login是class级别?
      不可以。

一个fixture,可以使用比它高的/与它同级的 fixture作为它的参数。 

   function,可以调用class,function,module,session.
   function最小单位。最后执行。其它的级别一定是比它先执行。


进去:校门 -> 楼大门 -> 教室门
出来:教室门 -> 楼大门 -> 校门
"""


test.login.py

from selenium import webdriver
import logging
from web_basepage.Common import logger

from web_basepage.PageObjects.login_page import LoginPage
from web_basepage.PageObjects.home_page import HomePage

from web_basepage.TestDatas import Common_Datas as CD
from web_basepage.TestDatas import login_datas as LD
import pytest


@pytest.fixture
def init(init_driver):
    logging.info("***** login用例自己的 init 的前置  *****")
    lp = LoginPage(init_driver)
    yield init_driver,lp   #(driver对象,lp页面对象)
    logging.info("***** login用例自己的 init 的后置  *****")


@pytest.mark.usefixtures("init")
class TestLogin:

    # 正常场景 - 登陆成功。
    def test_login_success(self,init):
        logging.info("******* 登陆功能 - 正常场景用例:登陆成功 *******")
        # 调用登陆页面的。登陆行为。
        init[1].login(LD.success_data["user"], LD.success_data["passwd"])
        # 断言 - 首页当中,应该存在 退出元素。
        assert HomePage(init[0]).check_user_exist()
        # 断言2 - url地址  应当为 http://120.78.128.25:8765/Index/index
        assert init[0].current_url == LD.success_data["check_url"]

    #unpace  解包两层如:[(),()]
    # 异常用例 - 用户名为空/密码为空/用户名格式不正确
    # @ddt.data(*LD.wrong_datas)
    @pytest.mark.parametrize("case",LD.wrong_datas)#不需要解包、case名称用来接收一组数据,需要一致
    def test_login_failed_by_wrongData(self,case,init):
        logging.info("******* 登陆功能 - 异常场景用例:数据格式校验 - 用户名为空/密码为空/用户名格式不正确 *******")
        # 调用登陆页面的。登陆行为。
        init[1].login(case["user"], case["passwd"])
        # 断言 - 登陆页面 - 应当现提示信息:请输入手机号
        self.assertEqual(case["check"], init[1].get_error_msg())
pytest.ini

[pytest]
markers=
    demo

test.demo.py

import pytest

pytestmark = pytest.mark.demo#打标记

def test_aa():
    print("11111111111111111111")

class Testaa:

    def test_demo(self):
        print("demo")

    def test_bb(self):
        print("bb")

    def test_false(self):
        print("====== 我是失败的用例 +++=====")
        assert False

main.py

import pytest

pytest.main(["-s","-v","-m","demo","--html=Outputs/report.html",
             "--reruns","2","--reruns-delay","5"])
#过滤demo用例,生成html报告,

  

#如果test_login.py有返回值要用,直接传mySs,如: def test_login_failed_by_wrongData(self,case,init,mySs):
#autouse=True, 除了session其它级别的不用开
conftest.py
@pytest.fixture(scope="session",autouse=True)
def mySs():
    print("**** 我是session级别的fixture 前置 ****")
    yield True #如有返回值
    print("**** 我是session级别的fixture 后置 ****")

test_demo.py
import pytest

def test_aa(mySs):
    print(mySs)


输出:True

conftest.py @pytest.fixture(scope
="session",autouse=True) def mySs(): print("**** 我是session级别的fixture 前置 ****") yield #无返回值 autouse自动执行了 print("**** 我是session级别的fixture 后置 ****" test_demo.py import pytest def test_aa(): print("11111111111111111111") 输出:11111111111111111111 conftest.py @pytest.fixture(scope="module") def myMo(): print("**** 我是module级别的fixture 前置 ****") yield print("**** 我是module级别的fixture 后置 ****") test_demo.py import pytest @pytest.mark.usefixtures("myMo") #在这个模块 再哪一行调用,它后面的才会生效,前面的不生效,一般使用不多, 如果要用一般是在模块的开始部位定义 def test_aa(): print("11111111111111111111") class Testaa: def test_demo(self): print("demo") def test_bb(self): print("bb")
原文地址:https://www.cnblogs.com/zcok168/p/13274224.html