返回顶部
扶摇直上九万里,展翅高飞岂可待。

接口测试——PyTest自动化测试框架(八)

1. PyTest介绍与安装

  1. PyTest介绍
  • PyTest是python的一个第三方的单元测试库
  • 自动识别测试模块和测试函数
  • 支持非常丰富的断言(assert)语句
  1. PyTest中的使用约束
  • 测试文件的文件名必须以"test_"或"_test"结尾
  • 测试类必须以“Test”开头
  • 测试的函数名必须以"_test"开头
  • 测试类里面不能使用"init"方法
  1. PyTest安装命令

pip install pytest

cd到这个文件
1675765513724.png

  • 输入1时打印结果为:
import pytest

def test_demo():
    assert 1 == 1

if __name__ =="__main__":
    pytest.main(['test_pytest1.py'])

1675765848977.png

  • 输入2时打印结果为:
import pytest

def test_demo():
    assert 1 == 1

if __name__ =="__main__":
    pytest.main(['test_pytest1.py'])

1675765889393.png

2. PyTest中的断言

  • 执行代码打开自己的文件
import pytest

def add(a,b):
    return a + b

def test_dengyu():
    assert 3 == add(1,2)


if __name__ == "__main__":
    pytest.main(['test_pytest2.py'])

1675766308640.png

  • 执行以下代码:
import pytest

def add(a,b):
    return a + b

def test_dengyu():
    assert 3 == add(1,2)

def test_budengyu():
    assert 5 != add(1,3)

def test_dayu():
    assert 5 > add(1,3)

def test_dayudengyu():
    assert 5 >= add(1,3)

def test_xiaoyu():
    assert 1 < add(1,2)

def test_xiaoyudengyu():
    assert 1 <= add(1,3)

def test_baohan():
    assert 1 in [1,2,3]

def test_bubaohan():
    assert 1 not in [1,2,3]

def test_iftrue():
    assert 1 is True

def test_iffalse():
    assert 0 is False

if __name__ == "__main__":
    pytest.main(['test_pytest2.py'])
  • 打印结果为三个失败,七个通过
D:\python\python.exe "D:/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5\test_pytest2.py 
Testing started at 18:45 ...
Launching pytest with arguments C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5\test_pytest2.py --no-header --no-summary -q in C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5

============================= test session starts =============================
collecting ... collected 10 items

test_pytest2.py::test_dengyu PASSED                                      [ 10%]
test_pytest2.py::test_budengyu PASSED                                    [ 20%]
test_pytest2.py::test_dayu PASSED                                        [ 30%]
test_pytest2.py::test_dayudengyu PASSED                                  [ 40%]
test_pytest2.py::test_xiaoyu PASSED                                      [ 50%]
test_pytest2.py::test_xiaoyudengyu PASSED                                [ 60%]
test_pytest2.py::test_baohan PASSED                                      [ 70%]
test_pytest2.py::test_bubaohan FAILED                                    [ 80%]
test_pytest2.py:26 (test_bubaohan)
1 != [1, 2, 3]

Expected :[1, 2, 3]
Actual   :1
<Click to see difference>

def test_bubaohan():
>       assert 1 not in [1,2,3]
E       assert 1 not in [1, 2, 3]

test_pytest2.py:28: AssertionError

test_pytest2.py::test_iftrue FAILED                                      [ 90%]
test_pytest2.py:29 (test_iftrue)
1 != True

Expected :True
Actual   :1
<Click to see difference>

def test_iftrue():
>       assert 1 is True
E       assert 1 is True

test_pytest2.py:31: AssertionError

test_pytest2.py::test_iffalse FAILED                                     [100%]
test_pytest2.py:32 (test_iffalse)
0 != False

Expected :False
Actual   :0
<Click to see difference>

def test_iffalse():
>       assert 0 is False
E       assert 0 is False

test_pytest2.py:34: AssertionError


========================= 3 failed, 7 passed in 0.21s =========================

Process finished with exit code 1

3. PyTest中的参数化

  • 执行以下代码三个通过,三个失败:
# 参数化
import pytest

def add(a,b):
    return a + b
#第一种实现参数化的写法
@pytest.mark.parametrize(['x','y'],[(1,2),(0,3),(1,4)])
def test_add1():
    assert 3 == add(x,y)

# 第二种参数化的写法
xy = [(-1,3),(-1,4),(1,-4)]
@pytest.mark.parametrize(['x','y'],xy)
def test_add2(x,y):
    assert 3 == add(x,y)

if __name__ == "__main__":
    pytest.main(['test_pytest3.py'])

1675767598244.png

# 参数化
import pytest

def add(a,b):
    return a + b
#第一种实现参数化的写法
@pytest.mark.parametrize(['x','y'],[(1,2),(0,3),(1,4)])
def test_add1():
    assert 3 == add(x,y)

# 第二种参数化的写法
xy = [(-1,3),(-1,4),(1,-4)]
@pytest.mark.parametrize(['x','y'],xy)
def test_add2(x,y):
    assert 3 == add(x,y)
# 把存储在mysql当中的测试用例参数发出来
import pymysql
db_info={
    "host":"192.168.0.108",
    "user":"root",
    "database":"db2"
    "charset":"utf8"
}
conn pytest.connect(**db_info)
cursor = conn.cursor()
sql = "select * from mumu"
cursor.execute(sql)
result = cursor.fetchall()

@pytest.mark.parametrize(['id',
                          'case_id',
                          'interface_type',
                          'uri',
                          'method',
                          'if_login',
                          'input_data',
                          'data_type',
                          'expect'],result)
def test_mysql_parm(id,case_id,interface_type,uri,method,
                          if_login, input_data,data_type,
                          expect):
    print(title)


if __name__ == "__main__":
    pytest.main(['test_pytest3.py'])

1675768761796.png

4. PyTest中的组织管理

  • 执行以下打印结果为9个都通过
# 测试用例的组织管理
import pytest

def test_01():
    print(1)

def test_02():
    print(2)

def test_03():
    print(3)

def test_04():
    print(4)

class TestLogin():
    def test_05(self):
        print(5)
    def test_06(self):
        print(6)
    def test_07(self):
        print(7)
    def test_08(self):
        print(8)
    def test_09(self):
        print(9)

if __name__ == "__main__":
    pytest.main(['test_pytest4.py'])
D:\python\python.exe "D:/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5\test_pytest4.py 
Testing started at 19:25 ...
Launching pytest with arguments C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5\test_pytest4.py --no-header --no-summary -q in C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5

============================= test session starts =============================
collecting ... collected 9 items

test_pytest4.py::test_01 PASSED                                          [ 11%]1

test_pytest4.py::test_02 PASSED                                          [ 22%]2

test_pytest4.py::test_03 PASSED                                          [ 33%]3

test_pytest4.py::test_04 PASSED                                          [ 44%]4

test_pytest4.py::TestLogin::test_05 PASSED                               [ 55%]5

test_pytest4.py::TestLogin::test_06 PASSED                               [ 66%]6

test_pytest4.py::TestLogin::test_07 PASSED                               [ 77%]7

test_pytest4.py::TestLogin::test_08 PASSED                               [ 88%]8

test_pytest4.py::TestLogin::test_09 PASSED                               [100%]9


============================== 9 passed in 0.06s ==============================

Process finished with exit code 0
# 测试用例的组织管理
import pytest

def test_01():
    print(1)

def test_02():
    print(2)

def test_03():
    print(3)
@pytest.mark.run(order=1)
def test_04():
    print(4)

class TestLogin():
    def test_05(self):
        print(5)
    def test_06(self):
        print(6)
    def test_07(self):
        print(7)
    def test_08(self):
        print(8)
    def test_09(self):
        print(9)

if __name__ == "__main__":
    pytest.main(['test_pytest4.py'])

"""
在pytest当中,测试方法执行的顺序,默认是从上到下
我们使用pytest.mark.run进行测试函数执行顺序的标记时
需要先安装pip inst pytest_ordering
"""
D:\python\python.exe "D:/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5\test_pytest4.py 
Testing started at 19:33 ...
Launching pytest with arguments C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5\test_pytest4.py --no-header --no-summary -q in C:\Users\Administrator\PycharmProjects\pythonProject\part2\chapter5

============================= test session starts =============================
collecting ... collected 9 items

test_pytest4.py::test_04 PASSED                                          [ 11%]4

test_pytest4.py::test_01 PASSED                                          [ 22%]1

test_pytest4.py::test_02 PASSED                                          [ 33%]2

test_pytest4.py::test_03 PASSED                                          [ 44%]3

test_pytest4.py::TestLogin::test_05 PASSED                               [ 55%]5

test_pytest4.py::TestLogin::test_06 PASSED                               [ 66%]6

test_pytest4.py::TestLogin::test_07 PASSED                               [ 77%]7

test_pytest4.py::TestLogin::test_08 PASSED                               [ 88%]8

test_pytest4.py::TestLogin::test_09 PASSED                               [100%]9


============================== 9 passed in 0.06s ==============================

Process finished with exit code 0

1675769700655.png

5. PyTest框架简介

  pytest,rf(学关键字语法,报告漂亮),unitest
  pytest是python的第三方单元测试框架,可以做系统测试,比unitest更简洁和高效,执行315种以上的插件,

    同时兼容unittest框架,在unittest框架迁移到pytest框架的代码不需要重写代码
  unittest框架迁移到pytest框架的时候不需要重写代码
  纯python代码的自动化测试框架

1:Pytest框架简介:
  接口测试方案:python

    一:工具类:纯手工测试,用工具来做(postman jemeter soapui)--入门简单,不好扩展(后面很多框架定制化)
    二:代码类:现成的python框架:unitest(单元测试比较多,最原始的解释器自带的,不需要安装,不支持定制化,分布式) pytest(高级,效率高,支持定制化) nose
      rf(报告篇评论,需要学会--封装关键字)
      pytest和nose都是unitest扩展的更高级的一个库,框架,基于unitest
    三:测试平台:现成平台,公司自己定制开发的,不对外 (融合jmeter,)  综合平台
      前端
      后端
      执行机制----框架pytest(一般融合了禅道,框架,邮件各种功能)

  pytest是python的第三方单元测试框架,可以做系统测试,比unitest更简洁和高效,支持315种以上的插件,

  同时兼容unittest框架,在unittest框架迁移到pytest框架的代码不需要重写代码
  unittest框架迁移到pytest框架的时候不需要重写代码
  纯python代码的自动化测试框架

  pytest对比unitest框架的优势:高级,效率高,支持定制化,支持分布式,支持315种以上的丰富插件,还能向下兼容unitest

  pytest灵活:

    1:定制化(定制化用例执行,定制化报告)

    2:环境清除也灵活   以及各方面做的都比unittest更加灵活

  pytest更加灵活,便捷,效率更高,  还支持分布式(分布式是其他框架做不了)

  分布式:1000个接口用例怎么跑,一条条跑很费劲,时间长,找几个同时分担执行测试用例(pytets独有的性质)

2:pytest框架环境搭建:
  pip pytest                安装pytest
  pip install pytest-html           安装原生态报告模板--自带的(有点垃圾)
  Required-by: pytest-xdist(分布式测试), pytest-metadata, pytest-html, pytest-forked, allure-pytest
  100个接口用例,正常是一个个用例跑,时间很长,
  分布式-多个业务用例多条线来跑,提高效率(分布式设计用例---分布式逻辑设计,不要出现耦合,关联性太强的东西,否则会等待的)
image.png

3:pytets执行测试用例
  设计测试用例时候注意点(必须遵循的规则,否则不识别):

    1:.py测试文件必须以test(test_xxx)开头(或者以_test结尾)
    2:测试类必须以Test开头,并且不能有init方法-----测试类Test开头
    3:测试方法必须以test_开头
    4:断言必须使用assert

4:一般做项目是新建package包的 
  项目文件
    lib库文件    (登录接口源代码,其他接口公共的类,封装的库,登录的,订单的)(包)
    data文件    (参数化数据,excel文件,yaml文件,csv文件---测试文件,用例,文档)(可以是普通文件夹)
    test_case文件  (放测试用例的 )(包)
      test_func01.py(测试用例,写的最好见名知意)
    report文件    (存放测试报告的普通文件夹)
    config      (配置文件)

5:pytest函数级别
  函数级别的测试用例必须test_开头:如下test_tc01,test_tc02两个测试用例

import pytest
    def test_tc01():    #定义函数类型测试用例  
        assert 1+1==2    #断言
    def test_tc02():
        assert 1+1==3    #断言
 
    if __name__ == '__main__':
        pytest.main(["test_func01.py"])        #我主动运行我的pytest框架(自动调用所有的test测试函数,按照顺序依次运行,test开头的用例自动识别)

6:pytest类级别(工作一般以类位单元,一个模块一个类,登录类,订单类,购物类)
  类级别的测试l类必须以Test开头,并且类李不能有init方法,类里面的函数都是test_开头

  封装好函数和类就行,其他的交给框架,设置好,框架帮你自动组织怎么运行

  封装为了分层,后面更好维护,代码结构整洁

import pytest
 
class Test_login():              #登录模块的测试类
  def test_login01(self):
    print("---test_login01----")
    assert 1 + 1 == 2
  def test_login02(self):
    print("---test_login02----")
    assert 1 + 1 == 3
if __name__ == '__main__':
  pytest.main(["test_func01.py","-s"])  #框架自己调用函数  需要打印对应的信息,需要在列表里面加-s

image.png

7:自动化测试里面的环境初始化与清除
  环境初始化目的:

    清空测试环境的垃圾数据,前置条件  

    需不需要分层:需要

    比如:课程模块:课程模块的初始化需要

      1:删除所有的课程   

      2:新增我们的一些课程(这个给修改/查询/删除接口使用)  模块级别的(大的课程模块第一件事就是删除以前的课程)

        干掉数据后假如需要删除课程,这个接口需要单独的fixture的初始化,增加课程才能删除,其他的接口不需要这个fixture初始化,)

      分层:模块层次的初始化,某个接口也需要初始化----框架的分层

      条件初始化要和接口挂钩,接口该怎么就要怎么设计

  环境初始化和清除,

    一头一尾,两个不同概念,(环境的初始化也可以是清除数据)
    一个接口可以多个级别的fixture,可以

    分布式:1:并行执行 2:分布式

    优化运行时间:分布式,(搭建环境麻烦)

  什么是环境初始化:

    做这个用例之前想要做个操作,初始化动作,比如登录,首先需要连上这个项目(要先能ping通),

    环境初始化--比如课程新增需要数据全部清空,也是环境初始化

  功能测试:保证测试环境数据和跑什么系统的,或者后台有什么进程执行,或者项目里面测试这功能,功能里面有没有垃圾数据要清除  做个初始化

  unittest:最基础的框架,python自带(环境初始化和数据清除用setup和teardown)

  jemeter:也有环境清除和初始化

  不管做什么测试比如(功能,自动化,性能)都要对当前测试环境初始化,做完后要垃圾数据进行回收(特别是自动化,不然很多用例明明是对的会失败)

    每次做一个场景,模块的时候,看看模块有没有需要前置的或者环境清除的步骤(基本操作流程)

  pytest是unittest的升级版,对环境清除操作非常灵活(分层分级)

  pytest:fixture操作类进行环境初始化 @fixture这样的一个装饰器

  pytest的fixture操作

    环境初始化与清除

    pytest提供的fixture实现unitest中的setup/teardown功能,可以在每次执行case之前初始化数据

    不同的是,fixture可以只在执行某几个特定case前运行,只需要在运行case前调用即可,比setup/teardown使用灵活

  pytest的初始化和清除可以类里面写个setup_class方法做,以类为单元,模块,包,方法为单元都可以,也可以用fixture来做

8:pytest前置和后置条件(环境初始化与清除)  
  环境初始化

    1:清除以前的数据

    2:测试的时候不是每个接口都要执行,可以定制化执行,固定执行某些接口,先执行删除用例,

      但是数据已经被清除了,无法删除,修改--需要新增一批测试数据,所以这时候需要环境初始化和清除的想法

  setup_class:类里面类级别的初始化,teardown

  pytest初始化和前置条件,很多接口用例本身需要初始化,初始化分为很多层,

  可以在整个外面做,也可以在里面做,测试类的初始化可以在类里面定义

 import pytest
    class Test_login():            #登录模块的测试类
        #该测试类---有个前置的操作(初始化)
        def setup_class(self):      #类级别的初始化--可选项
            #一个项目,先登录,再购物,登录就是购物类的前置条件,可以放在setup_class里面
            print("执行测试类之前,我需要执行操作")
 
        def test_login01(self):
            print("---test_login01----")
            assert 1 + 1 == 2
        def test_login02(self):
            print("---test_login02----")
            assert 1 + 1 == 3
 
        def teardown(self):            #看业务本身需不需要初始化和清除环境,--可选项
            print("------该测试类的环境清除-----")
 
    if __name__ == '__main__':
        pytest.main(["test_func01.py","-s"]) 

9:pyets种有四种级别的setup和teardown
  1:setup_module和teardown_module,在整个测试用例所在的文件中所在的文件中所有的方法运行前和运行后运行,只运行一次---模块的
  2:setup_class和teardown_class,在整个测试文件中的一个class中所有的用例的签后运行 ----class类
  3:setup_method和teardown_method,在class内的每个方法运行前后运行 ---------方法的
  4:setup_function和teardown_function,在非class下属的每个测试方法的前后运行 ----函数的
    分层分级(不同级别有不同方法)

10:pytest里面的数据初始化装饰器fixture参数说明
fixture_function: Optional[_FixtureFunction] = None,
*,
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function", --scope参数:级别
params: Optional[Iterable[object]] = None,------------------------------- params:参数
autouse: bool = False,---------------------------------------------------- autouse:是否自动化执行
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
] = None,
name: Optional[str] = None
 @pytest.fixture(scope=xxx,params=xxx,autouse=xxx) 

    fiixture装饰器可以传单三个参数

      1:scope参数:初始化清除定义级别

       2:params:参数

       3:autouse:是否自动化执行 

image.png

  11:fixture 函数级别的初始化,环境初始化

import pytest

                #函数级别的@pytest.fixture()初始化操作

@pytest.fixture()       #标记函数是个初始化操作,标记后需要传给每个函数statr_func这个函数名才会执行初始化操作(函数级别的)

def statr1_func():

                #这不是测试函数,一个普通函数,pytest执行用例只能识别test开头的方法和函数,所以pytest.main不会执行(不参加pytest用例)

    print("------初始化操作1------")

@pytest.fixture()

def statr2_func():

    print("------初始化操作2------")

#fixture:有哪些操作(可以多个初始化可以一起调,需要两个初始化,需要连接,需要登录)

#这种写法很方便,函数需要statr_func1函数做一个初始化操作可以调用statr_func1这个函数,---def test_001(statr1_func):

# 需要其他初始化方法可以选择性调用其他初始化函数,传递函数名就行(灵活选择)----def test_002(statr2_func):

#函数初始化操作需要传递几个函数也可以多个函数名传递--def test_003(statr2_func,statr1_func):

#方便灵活

def test_001(statr1_func):

    print("-----test01------")

def test_002(statr2_func):

    print("-----test02 ------")

def test_003(statr2_func,statr1_func):

    print("-----test03 ------"

if __name__ == '__main__':

    pytest.main(["test_pytest.py","-s"])

** 聪明的小伙伴就发现了,欸出bug了, print("-----test03 ------"这里明显少了个括号嘿嘿嘿,写代码还是要细心一点。记得加上【)】
**

 12:类级别的初始化class,可以使用setup做初始化,也可以使用fixture做初始化

 import pytest

       @pytest.fixture(scope="class")           #类级别的初始化函数  scope="class" 就是把这个初始化定义成类级别的
       def statr1_func():
           print("------初始化操作1------")

       class Test_00:                  #需要执行 Test_00测试类,需要做初始化(可以setup_class)
           # def setup_class(self):
           #     print("类内部的初始化,")          #只对类有用,类级别的,类里只做一次(几个类的初始化操作一样这种不适合,需要重复写)
                                                  #fixture初始化类就是避免重复代码
           def test_001(self,statr1_func):
               print("-----test01------")

           def test_002(self,statr1_func):
               print("-----test02 ------")


       if __name__ == '__main__':
           pytest.main(["test_pytest01.py","-s"])

  类级别初始化fixture,虽然test_001和test_002都调用了statr1_func这个类级别的初始化函数,但是执行类测试用例的时候只执行statr1_func初始函数一次
  
  多个类都可以调用statr1_func这个类级别的初始化方法,调用的时候最好放在类里的第一个函数,后面的函数可以不传(因为对应的是类级别的初始化)
  

  import pytest
       @pytest.fixture(scope="class")         #类级别的初始化函数
       def statr1_func():
           print("------初始化操作1------")
                                                   #一个模块里面有函数用例也有类的用例怎么做:(class级别的初始化只对类有用,对函数没用)
       def test_003(statr1_func):                  #测试函数,
           print("-----test03------")

       class Test_00:                        #需要执行test00测试类,需要做初始化(可以setup_class)
           def test_001(self,statr1_func):
               print("-----test01------")

           def test_002(self,statr1_func):
               print("-----test02 ------")

       if __name__ == '__main__':
           pytest.main(["test_pytest01.py","-s"])

初始化方法statr1_func定义成class类级别的,函数级别的测试测试用例test__003调用初始化函数会执行一次,
  class类级别的测试用例Test_00调用初始化函数会执行一次(一共执行两次)
  看级别的,整个模块的级别的化最好用module,否则有问题,fixture可以做return,会有返回值的,对应级别来做,
执行结果:
    test_pytest01.py
    ------初始化操作1------
    -----test03------
    .------初始化操作1------
    -----test01------
    .-----test02 ------

 13:类级别初始化实际代码:初始化操作是登录操作

#课程模块的测试类
      import pytest
      from lib.api_lib.lesson import Lesson
      from lib.api_lib.lesson import Login
      from tools.execlMethod import get_excelData
      import json
      import os

      @pytest.fixture(scope="class")           #类级别的初始化函数
      def start_func():
          global sessionid
          sessionid = Login().login('{"username":"auto","password":"sdfsdfsdf"}')

      class Test_lesson:
          #1:课程新增接口,前置条件登录(封装完一个方法后想办法做数据驱动),课程增加需要通过excel表用例来做
          @pytest.mark.parametrize("inData,repsData", get_excelData('2-课程模块', 2, 26))
          def test_lesson_add(self,start_func,inData,repsData):
              reps=Lesson(sessionid).lesson_add(inData)
              print(reps)
              assert reps["retcode"]==json.loads(repsData)["retcode"]

      if __name__ == '__main__':
          pytest.main(["test_lesson01.py", "-s", "--alluredir", "../report/tmp"])
          os.system("allure serve ../report/tmp")

image.png

 14:模块级别的初始化mudule,不管是类还是方法    @pytest.fixture(scope="module")
    模块(module)级别的初始化,(整个模块所有的类所有的东西要做一步操作,可以使用module这个模式)

    只在模块运行前面只做一次,后面不做了,哪怕多调用也没用,一个模块里面有test_003函数测试用例,

    也有classTest_00类级别的测试用例,定义一个模块级别的初始化函数statr1_func

    函数里面调用初始化方法def test_003(statr1_func):和类里面的方法调用初始化方法test_001(self,statr1_func):,test_001(self,statr1_func):

    整个模块执行的时候初始化函数都只执行一次(不管你这个模块里面调用多少次)

import pytest
       @pytest.fixture(scope="module")         #模块级别的初始化函数
       def statr1_func():
           print("------初始化操作1------")
           
                                 #一个模块里面有函数用例也有类的用例怎么做:(class级别的初始化只对类有用,对函数没用)
       def test_003(statr1_func):            #测试函数,
           print("-----test03------")

       class Test_00:                          #需要执行test00测试类,需要做初始化(可以setup_class)
           # def setup_class(self):
           #     print("类内部的初始化,")      #只对类有用,类级别的,类里只做一次(几个类的初始化操作一样这种不适合,需要重复写)
           #                                   #fixture初始化类就是避免重复代码

           def test_001(self,statr1_func):
               print("-----test01------")

           def test_001(self,statr1_func):
               print("-----test02 ------")

       if __name__ == '__main__':
           pytest.main(["test_pytest01.py","-s"])

     执行结果:test_pytest01.py
          ------初始化操作1------
          -----test03------
          .-----test01------
          .-----test02 ------

    在这个模块下面所有的都会调用(包级别的,包里面运行前做个环境清除)

    需要在testcase文件夹里面创建一个conftest.py模块,这个

    固定名称,pytest自动识别这个名称

    testcase里面:新增课程前面需要登录,增加课程前面需要清除数据,需要2个级别的初始化,1:登录 2:整个环境的清除

    test_case(测试用例文件夹)创建一个:conftest.py文件  里面写包级别的初始化

    conftest.py文件里也能写类级别和模块级别的初始化,而且不需要调用,这个conftest.py模块是pytest自动识别导入的

    test_case                #文件夹
        conftest.py           
            import pytest
            #包级别的初始化,在运行整个包之前做个初始化,包里面不同作用域,每个包里面都可以放一个,每个包里面的操作都可以不一样
            @pytest.fixture(scope="session",autouse=True)           #session级别的处于时候autouse=True默认自动执行
            def start_demo(request):                                     #包的开始
                print("我是整个包的初始化")
                def fin():                             #尾部这是包级别的,整个包做完后做个环境数据的清除    包的结束
                    print('---测试完成,包的数据清除---')

                request.addfinalizer(fin)                   #回调,当我整个包运行完了后回调fin这个方法

   #fixture的参数autouse: bool = False,---自动执行参数

    #session的级别,包里面有很多模块,很多模块需要对整个包进行初始化在conftest.py里面做模块的数据初始化和清除(conftest.py只对当前包有用)

  15:两种调用初始化和清除函数的方式+初始化清除函数的返回值的使用

import pytest
@pytest.fixture()
def befor_func():
   print('xxxxxxxxxxxxx测试用例的初始化xxxxxxxxxxxxxxxx')
   yield 10
   print('zzzzzzzzzzzzzzzzzz测试用例的清除zzzzzzzzzzzzzz')

def test_001(befor_func):                #调用初始化和清除方式一:直接在测试用例里传递初始化清除函数的函数名来调用
   print("测试用例001")
   res=befor_func                    #如果初始化清除函数有返回值,可以直接这样接收参数来使用
   print(res)

@pytest.mark.usefixtures('befor_func')        #调用初始化和清除方式二:使用usefixtures放在测试用例前面直接调用初始化清除函数
def test_002():
   print("测试用例002")

if __name__ == '__main__':
   pytest.main(["test1.py",'-s'])

  七:pytest前置条件+后置条件的两种写法

    1:使用yield关键字来是实现  推荐使用这种,因为yield关键字能返回函数的值

import pytest
@pytest.fixture()
def befor_func():
   print('xxxxxxxxxxxxx测试用例的初始化xxxxxxxxxxxxxxxx')
   yield 10                            #yield后面跟的是测试用例的后置条件,支持用例执行后就执行yield里的内容
   print('zzzzzzzzzzzzzzzzzz测试用例的清除zzzzzzzzzzzzzz')

def test_001(befor_func):
   print("测试用例001")
   res=befor_func
   print(res)

if __name__ == '__main__':
   pytest.main(["test1.py",'-s'])

2:使用finc()函数来实现    这种就不能返回返回值了

import pytest
@pytest.fixture()
def befor_func(request):
  print('xxxxxxxxxxxxx测试用例的初始化xxxxxxxxxxxxxxxx')
  def fin():                        #尾部这是后置条件,测试用例执行后就会调用这个函数      
      print('zzzzzzzzzzzz测试用例的清除zzzzzzzzzzz')

  request.addfinalizer(fin)               #回调,当我整个包运行完了后回调fin这个方法          
def test_001(befor_func):
  print("测试用例001")

if __name__ == '__main__':
  pytest.main(["test1.py",'-s'])

16:pytest数据驱动(参数化) 
  pytest数据驱动的意义:

    参数化(登录用例4条,每一个账号密码都不同,使用框架把4个用例全部执行完,不需要for循环遍历执行,采用数据驱动方案来做)

    pytest内置装饰器@pytest.mark.parametrize可以让测试数据参数化,把测试数据单独管理,类似ddt数据驱动的作用,方便代码和测试数据分离

  @pytest.mark.parametrize("a",[1,2,3]):        参数化传一组参数  

  @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)])    参数化传多组参数

  登录账户密码(name和psw不同的用例组合,一个接口几十个用例怎么做----几十组数据----传的参数不同(什么请求方式和各种都一样)

  可以把name和psw分别采取多组数据进行参数化,数据分离,一个接口跑4次,每次用不同的参数)

   import pytest
   #[(1,2),(3,4),(5,6)]   [1,2,3]
   class Test_login():  
       def setup_class(self):
           print("执行测试类之前,我需要执行操作")

       @pytest.mark.parametrize("a",[1,2,3])            #("变量名",[1,2,3]),数据需要封装成一个列表,多个数据需要封装成列表嵌套元组   ----数据驱动
       def test_login01(self,a):                  #数据驱动,一定要把变量名a引入引来,不然无法参数化
print("---test_login01----")
          assert 1 + 1 == a

      @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)])    #数据驱动传多组参数
      def test_login02(self,a,b):
           print("---test_login02----")
           assert a + 1 == b

      def teardown_class(self):
          print("------该测试类的环境清除-----")

  if __name__ == '__main__':
      pytest.main(["test_func01.py","-s"])  

17:pytest结合allure报告操作  
  一:pytest自带的报告框架 pytest-html

  二:allure环境搭建(allure是报告库不是python专属的,很全面的框架)-allure报告漂亮

    1:下载allure.zip(压缩包)
    2:解压allure.zip到一个文件目录
    3:将allure-2.13.3\bin路径添加到环境变量path
    4:pip install allure-pytest -------allure报告本身不是很漂亮,通过allure-pytest这个库可以定制化报告,让报告变得很漂亮
    5:验证(cmd输入allure)

  三:allure和pytest联合执行生成报告:运行两条语句

    1:执行pytest单元测试,生成的allure报告需要的数据存在/tmp目录

    pytest -sq --alluredir=../report/tmp   #pytest把allure报告的生成的中间文件放到一个临时文件里面(pytets生成报告,需要数据,所以先把数据存起来)

                      #所有的报告需要数据支持的,数据来源pytest框架本身,结果数据存到一个文件,存在../report/tmp文件夹 

                      #tmp临时文件,一般json格式      

    2:执行命令,生成测试报告

    allure generate ../report/tmp -o ../report/report -clean       #allure指令生成对应报告

  18:allure模拟代码

import pytest
     import os
     class Test_login():  
         def setup_class(self):
             print("执行测试类之前,我需要执行操作")

         @pytest.mark.parametrize("a",[1,2,3])
         def test_login01(self,a):  
             print("---test_login01----")
             assert 1 + 1 == a

         @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)])
         def test_login02(self,a,b):
              print("---test_login02----")
              assert a + 1 == b

         def teardown_class(self):
             print("------该测试类的环境清除-----")

     if __name__ == '__main__':
                         #需要打印对应的信息,需要在列表里面加-s
                         #1:--alluredir ---生成临时文件,测试用例的结果数据放到目录   --alluredir   存放目录
         pytest.main(["test_func01.py","-s","--alluredir","../report/tmp"])  #框架自己调用函数
                         #通过--alluredir把allure需要的数据存到../report/tmp这个路径下面
                         #../--所在路径的父级别目录是test_case的目录隔壁邻居report文件下tmp,专门放alluer报告生成的需要的数据源

                         # 2:临时数据没有报告的,allure generate allure才会生成报告   -----allure生成器生成allure报告--generate allure生成器,cmd指令
                         #需要os模块os.system()调用指令可以在local的cmd里面敲
         os.system("allure generate ../report/tmp -o ../report/report --clean")
                         #os.system("allure generate 报告需要的数据 -o 报告存放目录 --clean")
                         #-o生成
                         #allure generate生成报告指令,把../report/tmp 的文件-o生成报告out out一下,生成的报告放在../report/report
                         #--clean把上次报告清除一下用--clean
                 #allure报告生成的是一个服务,(本地服务)和jinkins结合,放在整个里面去集成,放到公共服务器里面          

  19:allure报告的优化

import pytest
       import os
       import allure       
       @allure.feature("登录模块")                                                      #一级标题,大模块标题(类标签)
       class Test_login():                                                              
           def setup_class(self):                                                                  
               print("执行测试类之前,我需要执行操作")

           @allure.story("登录login01")                                                  # 二级标签(每个接口的标签)
           @allure.title("login01")                                                     # 标题,每个用例带个标题(报告体现在每个测试用例)(一个接口有几个用例,title用例的标签)
           @pytest.mark.parametrize("a",[1,2,3])                                      
           def test_login01(self,a):                                                      
               print("---test_login01----")
               assert 1 + 1 == a

           @allure.story("登录login02")                                                  # 二级标签,定制allure报告层级
           @allure.title("login02")                                                     #标题,每个用例带个标题(报告体现在每个测试用例)
           @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)])                      
           def test_login02(self,a,b):
                print("---test_login02----")
                assert a + 1 == b

           def teardown_class(self):                                               
               print("------该测试类的环境清除-----")


       @allure.feature("购物模块")
       class Test_Shopping():
           @allure.story("shopping")
           @allure.title("shopping01")
           @pytest.mark.parametrize("a,b", [(1, 2), (3, 4), (5, 6)])
           def test_shopping(self, a, b):
               print("---test_login02----")
               assert a + 1 == b
       if __name__ == '__main__':
           pytest.main(["test_func01.py","-s","--alluredir","../report/tmp"])    
           os.system("allure generate ../report/tmp -o ../report/report --clean")
           #allure报告生成的是一个服务,(本地服务)和jinkins结合,放在整个里面去集成,放到公共服务器里面     

 20:其他知识点
    测试用例一般写在excel表格文件里面,数据分离(维护好excel就行)

    pytest--从头到尾到报告执行发邮件

    字典是一种存储类型,json是一种格式(完全不同)

21:pytest参数解析
 

 pytest.main(['test_boss.py','-s','-k test_modify_psw','--alluredir=tmp/my_allure_results'])

    test_boss.py         指定测试用例文件,
    -s              显示print语句
    -k test_modify_psw      指定某个测试用例

    -n             表示用两个进程启动测试脚本

    生成报告缓存文件  --alluredir=tmp/my_allure_results

    os.system('allure serve tmp/my_allure_results')    打开测试报告,命令行需要python 的os模块调用

22:pytest的初始化和清除:

import pytest
       #假设启动被测app的时候需要去填写配置项信息,每个的端口号不同,多终端需要两个appim server
       #这时候setup_module和teardown_module不能传参,搞不定,需要换一种方法做测试用例的初始化和清除,

                       #setup_module以模块为作用域,不写module以测试用例(测试函数)为作用域
       # def setup_module():    #测试用例之前执行,原始的setup和teardown有个缺陷,里面不能传参数,
       #                      #默认test级别,每个测试用例执行的时候都会执行一次,希望当前某个模块执行的时候只执行一次(不管里面用例执行多少次)
       #                      #setup初始化和tear_down升个级,升级成module模块级别的
       #     print("启动被测app")
       #     print('连接appium服务')
       #
       # def teardown_module():
       #     print('关闭被测app')
       #     print('断开appium服务')

       #定义个函数,名字随便取  使用@pytest.fixture装饰器把这个函数装饰成初始化清除函数
       @pytest.fixture(scope='module')    #作用域默认test,初始化,加装饰器,初始化清除函数,autouse=True(自动执行)这种方法不建议使用                               #
       def before_test():                   #初始化函数升级作用域到module模块级别
           print("启动被测app")
           print('连接appium服务')
           yield   #后面写清除动作,
           after_test()

       #清除函数,清除函数并不会直接被初始化函数使用,我们必须放在初始化函数yiled后面才能回被调用
       def after_test():
           print('关闭被测app')
           print('断开appium服务')
       #目前一共有两个port,需要测试两个手机,两个多终端,before_test需要装饰器标记

       #测试用例的参数化
       @pytest.mark.usefixtures('before_test')                        #这表示调用某个自定义的初始化函数,括号里面的字符串写被调用函数的名字
       @pytest.mark.parametrize('psw',['boss123','boss456'])
       def test_app(psw):                        #测试用例,可能涉及到其他参数,比如需要一些配置信息,测试用例涉及到参数,
                              #多组参数需要使用装饰器pytest.mark.parametrize(数据驱动),psw传参和形参名字对应的
           print('测试boss app')
           print(f'登录测试账号{psw}')

       if __name__ == '__main__':
           pytest.main(['pytest_ywt.py','-s'])

23:pytest之:不只是测试函数test_app能参数化,初始化函数before_test也能参数化
  重点:测试用例的参数化+初始化清除函数的参数化  初始化清除函数的参数化能够实现appium的多终端测试

    初始化清除函数的参数化,方法很多种:

    before_test初始化函数注入参数,因为print(f'连接appium服务{port}')里面port需要变化的,

    @pytest.fixture(scope='module',params=[(4723,),(4727,)])  :初始化清除函数的参数化

      始化函数装饰器里面加params参数传参,port=request.param[0] 来调用params里的参数

      #初始化清除函数的参数化:只传单个参数
import pytest
@pytest.fixture(scope='module',params=[(4723,),(4727,)]) #初始化清除函数的参数化params
def before_test(request):
port=request.param[0]               #param[0],假如注入多个参数一个port和一个data--需要params传元组,
#params=[(4723,100),(4727,200)],一个参数的话不需要写成列表嵌套元素,
                                      #params[0]代表获取元组第一个
print("启动被测app")
print(f'连接appium服务{port}')
yield #后面写清除动作,
after_test()
                              #request是pytest的对象,我们在用对象里面的方法的时候pycham不会自动帮我们取显示名字,
                                      #它也不知道request里面到底什么内容


def after_test():
               print('关闭被测app')
               print('断开appium服务')

           @pytest.mark.usefixtures('before_test')    
           @pytest.mark.parametrize('psw',['boss123','boss456'])
           def test_app(psw):                                   
               print('测试boss app')
               print(f'登录测试账号{psw}')

           if __name__ == '__main__':
               pytest.main(['pytest_ywt.py','-s'])      
      #初始化清除函数的参数化:传多个参数              
       import pytest
       @pytest.fixture(scope='module',params=[(4723,'xiaomi'),(4727,'meizu')])  
           def before_testquest):           
               port=request.param[0]          #param[0],假如注入多个参数一个port和一个data,需要params传元组,params=[(4723,100),(4727,200)],
                                               #一个参数的话不需要写成列表嵌套元素,request.params[0]代表获取元组第一个
               device=request.param[1]          #request.param[1]对应元素里面第二个参数,
               print(f"在{device}启动被测app")
               print(f'连接appium服务{port}')
               yield   #后面写清除动作,
               after_test()
               
                                   #request是pytest的对象,(固定写法:request.param)
                                #我们在用对象里面的方法的时候pycham不会自动帮我们去显示名字,它也不知道request里面到底什么内容
        

  def after_test():
               print('关闭被测app')
               print('断开appium服务')

           @pytest.mark.usefixtures('before_test')                    
           @pytest.mark.parametrize('psw',['boss123','boss456'])
           def test_app(psw):           
                                       
               print('测试boss app')
               print(f'登录测试账号{psw}')

           if __name__ == '__main__':
               pytest.main(['pytest_ywt.py','-s'])

24:pytest框架执行代码也能在cmd里面直接输入命令执行
    xxx\test_case> pytest -s   在test_case这个目录执行会运行test_case文件里面所有的测试文件(test开头的测试用例)

25:分布式
  设置用例每个模块独立,有什么前置做到模块里面,比如测试10个模块,用相关联来做,不能做分布式(并发执行)

  每个模块独立还能定制执行那个模块,关联性太强做不到  

  最好做到每个接口都独立化(前置条件做好)不要做太大关联性的接口

  每一层都能做环境清除和定制化(包,模块。类,函数)分层,为后面mark(定点执行哪些用例)和分布式打基础

  分布式:必须做到用例的隔离(低耦合,高内聚),用例走串行风险很大,很难维护

    3000个请求,全部独立化,然后分布式来做(效率提高几倍--几十倍)

26:分布式的实现
  分布式的核心点:封装设计:相互独立,登录和课程相互独立,至少模块为单元要相互独立,封装相互独立,接口用例之间最好也相互独立  才能进行分布式

  一:pytest分布式环境搭建和理论介绍:

    第一步:安装一个库 pip install pytest-xdist   分布式运行插件,可以做分布式(这个库有两种运行方式)

    运行方式:

      1:串行,顺序运行,串行运行,从头到尾

      2:并行:pytest-xdist做分布式有两种,一种多核,一种多台机器

        一:多核:单机多核来做(同时跑) 使用-n参数

          电脑多核有假有真:超线程技术(8内核搞成16核),真8核假8核---

          cpu个数:硬件,几个cpu槽,i9900.i710--一般电脑就算一个cpu,单cpu,服务器可能有多个cpu

          核数: 电脑的核数,

          逻辑核数:逻辑核数可以虚拟化,8核可以变成16核(超线程技术)

          多核的话xdist本身的多核的话一般用逻辑核数来做的

        二:多机(可以使用虚拟机)---需要搭环境,多台机器 很麻烦,装环境,下库

  二:测试用例比较多怎么办:分布式 两种情况  

    1:量大:多机 (需要文件报告收集还需要搭环境,做起来比较麻烦)

    2:单机多核  很简单,加-n 参数就行 (做ui和需要一些时间等待的时候时间优化特别明显)

    串行运行:本身是线程去跑的,python就一个进程,里面很多线程,

    走进程的话需要多台机器来做,分量, 用例设计不好会有大问题,数据不对(用例一定要独立化)

    并行和多机:用例一定要设计好,不然数据容易出错,逻辑独立(不能有任何关联,不能有前后关系)  数据和代码封装时候独立化

  三:分布式运行代码

#验证单机多核分布式
import pytest
  import time

  def test_01():
      time.sleep(3)
      print("-----test01-----")

  def test_02():
      time.sleep(3)
      print("-----test01-----")


  if __name__ == '__main__':
      # pytest.main(["test_xdist.py","-sq"])          #这是串行跑的   6s
      pytest.main(["test_xdist.py", "-sq","-n","8"])     #单机多核并行   加"-n","8" 参数  ,用8个核来跑,5.41s,时间少了
      #或者测试用例文件目录下 cmd,输入 pytest test_xdist.py -n 8  也可以    这样cmd里执行看到的结果更直观,
      #多核来跑在ui里面时间提升很大,ui里面很多地方需要sleep,等待元素(有等待的的提升比较大)多核跑更快
          #有等待的情况用多核跑效果越明显
    串行运行本身按照线程去跑的,python本身就一个进程,里面很多线程 
    走进程的话,多台机器做比较合适---分量,
    用例一定要设计好,不然数据容易出错,逻辑独立(不能有任何关联,不能有前后关系)----数据和代码封装时候独立化

pytest cmd执行多个模块用例:pytest test_xdist.py test_login.py -sq   :运行两个.py文件(写多个运行多个)
pytest cmd执行多个包的用例:pytest test_xdist test_login -sq   :运行test_xdist包和test_login 包
还可以运行不同模块的两个包,加包路径 case/test_xdist.py case2/test2_xdist.py
27:pytest的用例定制化执行  mark标签,
  所有的接口不需要全部都跑(冒烟,定制化执行某些指定的业务,)     "-m","test_lesson_add" 

  一:pytest框架mark标签  标记非常丰富  mark标签

   mark标签:对于pytest,我们可以再每一个模块,每一个类,每一个方法和用例前面都加上mark,

        那样我们在pytest运行的时候就可以只运行带有该mark标签的模块,类,用例  

        这样的话可以方便我们选择执行自动化时,是执行全部用例,某个模块用例,

        某个流程用例,某个单独用例,总之就是可以某个单独的标签下所有用例

  mark可以标记不同层次的东西(类,函数,方法都可以标记)文件不用标记(本身就可以定制化执行)

  @pytest.mark.lessson_moudle   给测试类贴个标签,标签名字叫lessson_moudle标识课程模块,

    各个函数,类都可以贴上标签(类似别称),选择某个标签就运行某一个(灵活方便)

  什么都不选中照常运行,(全部运行,没有限制)

  mark标签pytest运行可能报错,

    PytestUnknownMarkWarning报错:是一个标签的mark警告,整个pytest这么写不识别你,但是不会报错,只是警告,

    消除警告(增加标签栏,相当于标签的声明)

    标签声明写法:teach_sq文件夹里创建一个pytest.ini的文件(pycham需要安装ini插件 file-setting-plugins(搜索ini)社区版似乎不行)

      pycham找不到可以离线装

        teach_sq
            pytest.ini-----文件内容如下,相当于pytest的mark标签声明一下

            #文件内容,markers后面把标签全部写上,    lessson_moudle 一个类级别的mark标签名,
            #test_lesson_add,test_lesson_list,test_lesson_delete 三个函数级别的mark标签名称
            #  mark标签名称: 描述 这样的格式来写,前面声明标签名称,后面是描述(随便写)

                [pytest]                    
                markers=
                    lessson_moudle: teach_lesson        (标签名: 描述  这样的格式   前面lesson_modle和test_lesson_add等是mark的标签名称,

                                        声明一个标签    :冒号后面一定要加空格,规范)

                                                            后面的描述不建议写中文,会报错(需要转码,但是这个文件python自己调用的)
                    test_lesson_add: teach_lesson
                    test_lesson_list: teach_lesson
                    test_lesson_delete: teach_lesson

  定制化执行test_lesson_add一个接口: "-m","test_lesson_add"

  pytest.main(["test_lesson01.py","-s","-m","test_lesson_add"]) ---- -m就可以实现,mark定制化执行

  定制化执行多个接口--逻辑或就行:"-m","test_lesson_add or test_lesson_delete"

  pytest.main(["test_lesson01.py","-s","-m","test_lesson_add or test_lesson_delete"])

  排除法排除一个-定制化除了某个接口不运行其他都运行---"-m","not test_lesson_add"

  pytest.main(["test_lesson01.py","-s","-m","not test_lesson_add"])----除了test_lesson_add这个接口其他都运行

  排除法排除多个-"-m","not (test_lesson_add or test_lesson_delete)"

  pytest.main(["test_lesson01.py","-s","-m","not (test_lesson_add or test_lesson_delete)"])

  筛选测试用例代码:

import pytest

@pytest.mark.zzzzz
def test_001():
   print('test_001')

def test_002():
   print('test_001')

if __name__ == '__main__':
   pytest.main(["test1.py",'-s','-m','zzzzz'])
posted @ 2023-03-20 10:51  孙凯玉  阅读(391)  评论(0编辑  收藏  举报