Python----Pytest 单元测试框架

1.说明

    官方文档:https://docs.pytest.org/en/stable/index.html

pip install pytest      #使用pip安装pytest库

   目录:

          1.说明

          2.pytest框架约束详解

          3.pytest数据驱动

          4.pytest fixture scoop 定义不同域执行前后需执行的方法

          5.获取pytest执行状态,根据状态执行不同的方法

 

 

2.pytest运行状态

    passed ---->Exit code 0: 所有测试均已收集并成功通过
    failed------->Exit code 1: 已收集并运行测试,但某些测试失败
                        Exit code 2: 测试执行被用户中断
    error-------->Exit code 3: 执行测试时发生内部错误
                        Exit code 4: pytest命令行使用错误
                        Exit code 5: 未收集任何测试
    xfail:预期失败,加了 @pytest.mark.xfail()

 

 

 

3.pytest框架约束详解 

 在pytest框架中,有如下约束:

       (1)在单测文件中,方法名称以 test  开头的方法使用pytest可以运行

       (2)在单测文件中,测试类以 Test 开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)

       (3)在单测文件中,可以包含一个或多个test_开头的函数。执行类里面的方法,类名以 Test 开头(看1,2的详解即可)

       (4)在目录中,文件名以 test_xxx / Test_xxx  模式,可以使用pytest运行

 

  pytest框架运行命令:

       pytest  1.py  -v                  # 使用pytest 执行时 添加 -v 参数,可以显示出详细的运行结果

       pytest 1.py   -s  -v              # -s 打印出测试用例中print语句

       pytest  1.py  -s  -v   --tb=no    # --tb=no :只显示关键的报错信息

       pytest  1.py::test_01  -s  -v     # 1.py::test_01 表示只执行1.py文件中test_01方法

      

       pytest   demo    -s  -v           # 运行demo目录下,所有满足条件的文件

       pytest   demo/1.py   -s  -v       # 运行demo目录下1.py文件

  ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(1)在单测文件中,方法名称以 test  开头的方法使用pytest可以运行 详解

"""
   第十八节课内容 : python类-多态,pytest框架
       1.类-多态
       2.pytest 框架
"""

# 当前文件 1.py
# 2.pytest框架
# 在单测文件中,方法名称以 test  开头的方法使用pytest可以运行

def test_01():  # test开头,pytest可运行
    assert 1 == 1


def test():  # test开头,pytest可运行
    assert 1 == 1


def Test():
    assert 1 == 1


def a():
    assert 1 == 1

def b_test():
assert 1 == 1

使用pytest运行,运行结果:

 

(2)在单测文件中,测试类以 Test 开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的) 详解

# 在类中使用pytest
# 在单测文件中,测试类以 Test 开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)

class TestUser:   # 类以 Test 开头,pytest可运行
    # 普通方法
    def get_user_info(self):
        assert 1 == 1

    # 测试方法
    def test_login(self):   # 方法以 test 开头,pytest可运行
        assert 1 == 1

    def test_zhuce(self):   # 方法以 test 开头,pytest可运行
        assert 1 == 1


class Test_aa():   # 类以 Test 开头,pytest可运行
    def test_a(self):   # 方法以 test 开头,pytest可运行
        assert 1 == 1


class Test_bb():  # 类中有init 方法,整个类都不能运行
    def __init__(self):
        assert 1 == 1

    def test_b(self):
        assert 1 == 1


class test222:    # 类以 Test 开头,pytest可运行 ;以test开头则不能运行
    def test_c(self):
        assert 1 == 1

使用pytest运行,运行结果:

 

(4)在目录中,文件名以 test_xxx 模式,可以使用pytest运行

         每个文件中,均定义了一个,pytest可运行的方法

 

 使用pytest运行,运行结果:

 

 

 

4.pytest数据驱动

# 当前文件 5.py
#
3.pytest_数据驱动 test_data = [ ('test1','123456'), ('','123456'), ('!@@#$%^','123456'), ('test1','#$%^&*'), ('','') ] import pytest class TestUser: @pytest.mark.parametrize('name,pwd',test_data) def test_login(self,name,pwd): print(f'登陆系统~~~name={name} ,pwd={pwd}')

使用pytest运行,运行结果:

 

 

 

5.pytest fixture scoop 定义不同域执行前后需执行的方法

设置fixture 的scope ,官方文档地址:https://docs.pytest.org/en/stable/fixture.html#fixtures

在执行自动化脚本的时候,有时需要在执行所有的用例之前或者所有的用例之后要进行某些操纵。比如:在运行所有的用例之前需要对数据库进行恢复操作。

在项目的根目录下创建 conftest.py , pytest 在运行的时候会自动加载此文件中定义的方法。

实战:(点击下载实战代码)

# test_1.py 文件

def test_noe():
    print('----noe方法----')

def test_noe2():
    print('----noe2方法----')


class Test_A:
    def test_a1(self):
        print('----a1方法----')

    def test_a2(self):
        print('----a2方法----')


class Test_B:
    def test_b1(self):
        print('----b1方法----')

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

# test_2.py 文件


def test_fir():
    print('----fir方法----')


class Test_C:
    def test_y1(self):
        print('----y1方法----')

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

# pytest配置文件 conftest.py 文件
import pytest

'''
scope='session'  指定定义的方法执行域为所有用例运行之前
autouse=True     运行该方法,不写的话,方法不会执行
'''


@pytest.fixture(scope='session',autouse=True)
def session_A():
    # 定义所以用例运行之前的操作......
    print('\n准备,用例要开始运行了.......')
    yield
    # 定义所以用例运行之后的操作......
    print('结束,所有用例运行完成......all_over')


'''
scope='module'  指定定义的方法执行域为所有py文件运行之前
autouse=True   运行该方法
'''


@pytest.fixture(scope='module',autouse=True)
def module_B():
    print('\npy文件执行之前......')
    yield
    print('py文件执行之后.......完成')


'''
scope='class'  指定定义的方法执行域为所有class类运行之前
autouse=True   运行该方法
'''


@pytest.fixture(scope='class',autouse=True)
def class_C():
    print('\nclass类运行之前......')
    yield
    print('class类运行之后......')


'''
scope='function'  指定定义的方法执行域为所有def函数运行之前
autouse=True      运行该方法
'''


@pytest.fixture(scope='function',autouse=True)
def function_D():
    print('\n函数执行之前......function')
    yield
    print('函数执行之后......function')

运行结果:

 

 

 

6.获取pytest执行状态,根据状态执行不同的方法

# test.py 文件

def test1():
    assert 1==0   # 执行不通过

def test2():
    assert 1==1   # 执行通过

 

# pytest 的配置文件 conftest.py
import pytest
from _pytest.reports import TestReport
from _pytest.runner import CallInfo
from pluggy.callers import _Result
from py._code.code import ExceptionInfo

# 获取测试结果报错信息和报错代码
# 注意:方法名固定
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call:CallInfo):
    outcome:_Result = yield
    # 获取测试结果,通过 call 来访问到具体的报错
    testreport:TestReport = outcome.get_result()
    if testreport.when == 'call' and testreport.outcome == 'failed':
        print('出问题的代码---->',testreport.nodeid, '执行失败')
        if call.when == 'call':
            # print(call.excinfo)
            excinfo: ExceptionInfo = call.excinfo
            # 报错信息
            print('报错信息----->',excinfo.value)
            # 报错的代码
            print('报错的代码---->',excinfo.traceback)
  • when='setup'          表示用例执行之前
  • when='call'             表示用例执行之中
  • when='teardown'    表示用例执行之后
  • outcome='passed'  表示执行结果(当前结果:通过)

运行结果:

0
 
 

优化conftest.py

增加  每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式  的相关处理

from appium import webdriver
import pytest
import os, time
chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),'drivers/chrome/75.0.3770.140/chromedriver.exe')

# scope='session' 标记的方法执行域为---->所有测试用例运行之前/之后 运行的方法
@pytest.fixture(scope='session',autouse=True)
def driver():
    desired_caps = {
        'platformName': 'Android',                    # 测试Android系统
        'platformVersion': '7.1.2',                   # Android版本 可以在手机的设置中关于手机查看
        'deviceName': '127.0.0.1:62001',              # adb devices 命令查看  设置为自己的设备
        'automationName': 'UiAutomator2',             # 自动化引擎
        'noReset': False,                             # 不要重置app的状态
        'fullReset': False,                           # 不要清理app的缓存数据
        'chromedriverExecutable': chromedriver,       # chromedriver 对应的绝对路径
        'appPackage': "org.cnodejs.android.md",       # 应用的包名
        'appActivity': ".ui.activity.LaunchActivity"  # 应用的活动页名称(appium会启动app的加载页)
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_capabilities=desired_caps)
    driver.implicitly_wait(5)  # 全局的隐式等待时间
    yield driver  # 将driver 传递出来
    driver.quit()


# 该方法是用来获取测试用例执行的结果(passed / FAILED)
@pytest.hookimpl(tryfirst=True,hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()               # 获取用例的执行结果
    print('用例的执行结果rep---->',rep)
    setattr(item, "rep_" + rep.when, rep)    # 将执行结果保存到 item 属性中 , req.when 执行时


# scope='function' 标记的方法执行域为---->每个测试用例运行之前/之后 运行的方法
@pytest.fixture(scope='function',autouse=True)
def case_run(driver:webdriver,request):   # request 为 pytest_runtest_makereport 方法获取到的执行结果(固定参数和用法)
    yield
    # 每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式
    if request.node.rep_call.failed:
        screenshots = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'screenshots')
        if not os.path.exists(screenshots):
            os.mkdir(screenshots)
        casename: str = request.node.nodeid
        # print("执行测试用例的名字:", casename)
        # 测试用例的名字很长 testcases/test_ddt/test_ddt_login.py::TestDdtLogin::test_login[....]
        # 对字符串进行截取,截取之后显示为  test_ddt_login-TestDdtLogin
        casename = casename[casename.rfind('/')+1:casename.rfind('::')].replace('.py::','-')
        filename = casename + '-' + time.strftime('%Y_%m_%d-%H_%M_%S')  +".png"
        screenshot_file = os.path.join(screenshots, filename)
        # 保存截图
        driver.save_screenshot(screenshot_file)

 

 
 
posted @ 2021-03-01 18:20  Z_sun  阅读(159)  评论(0编辑  收藏  举报