目录

80节

1.前置后置条件----测试夹具fixture

2.conftest.py 文件

3.重运行机制 

 

1.测试夹具fixture

背景:之前的测试代码中,并没有实现测试完成后,关闭浏览器操作的行为。

这么多年测试经验的你,不难理解:打开浏览器、关闭浏览器,可以看做是前置条件和后置条件。

那么在unittest中的setUp() 、tearDown()在这里还能不能用呢?答案是否定的,因为pytest的类在使用fixture是不兼容unittest的,不能继承unittest.TestCase,就无法使用setUp() 、tearDown()。(setUp() 、tearDown()是unittest框架里的功能啦)

智能的pytest,提供了一套系统,作为前置、后置条件---------->测试夹具

 

申明测试夹具实现语法:

①定义的普通函数(浏览器函数(这里定义函数名是driver))上,添加@pytest.fixture(),申明这是个测试夹具

②把return driver改为 :yield driver ,实现前置、后置条件(yield前面的就是前置条件,后面的就是后置条件)

   yield可以理解为return,有返回值------区别是,执行yield,其后面的代码会继续执行,执行return,其后面的代码不会执行

③后置清理语句:driver.quit()

 

测试夹具如何使用:

将测试夹具的定义的函数名,作为参数,放到测试用例的函数中------>def test_login_error(self,test_info,driver):

 

程序运行顺序:

测试用例的方法中要用到driver,调用driver这个测试夹具,再执行测试用例的方法,最后再执行后置清理)

 

代码实现如下: 

"""登录功能的测试用例"""

import pytest
from middware.handler import HandlerMiddle
from config.config import Wait_time

data = HandlerMiddle.excel.read_data("login")

@pytest.fixture()
def driver():
    from selenium import webdriver

    ##前置条件
    # 1.打开浏览器
    driver = webdriver.Chrome()
    # 设置隐性等待 等待的时间就可以放在config中,直接参数调用
    ##方法一:放在yaml中
    wait_time = HandlerMiddle.yaml_data["selenium"]["wait_time"]
    ##方法二、放在config.py中
    # wait_time = Wait_time
    driver.implicitly_wait(wait_time)

    yield driver

    #后置q清理
    driver.quit()


@pytest.mark.login
class TestLogin():
    """登录功能的测试类"""
    @pytest.mark.smoke
    @pytest.mark.error
    @pytest.mark.parametrize("test_info",data)
    def test_login_error(self,test_info,driver):
  。
  。
  。

 

知识拓展:yield

1).生成器generator的概念

 在Python中,一边循环一边计算的机制,称为生成器:generator。

2).为什么要用生成器? 

列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。

如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。

简单一句话:我又想要得到庞大的数据,又想让它占用空间少,那就用生成器!

3).怎么创建生成器?-------2种方法

①只要把一个列表生成式的[]改成(),就创建了一个generator

>>> My_list = [x * x for x in range(10)]
>>> My_list
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]    #返回的是一个存放数据列表,数据量大,会很占内存
>>> gen = (x * x for x in range(10))
>>> gen   #返回一个生成器,需要哪些数据,在循环的过程中不断推算出后续的元素,节省空间
<generator object <genexpr> at 0x1022ef630>

 L是一个My_list,而gen是一个generator

带有 yield 关键字的函数,那么这个函数就不再是一个普通函数,而是一个generator。调用函数就是创建了一个生成器(generator)对象。

 举例,yield实现斐波那契数列

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b      # 使用 yield
        a, b = b, a + b 
        n = n + 1
 
for n in fab(5): 
    print n

如果这里的yield用 print 打印数字,会导致该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列。

调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

 

“圆规”正传,回到fixture~~~~~~~~~~~

 在unittest中,有setUpClass,tearDownClass,那么在pytest中的fixture应该如何实现呢??

在@pytest.fixture()中添加参数scope=“class”,在运行程序时,每个测试用例的类,只会执行一次。

※:pytest运行程序时,捕获所有的输出,加-s (不加-s的话,输出信息不会显示)

  即pytest -m  "tagname" -s

举个栗子:

不加scope=“class”(默认scope="function")

import pytest
#申明测试夹具
@pytest.fixture()
def fixt():
    #前置条件
    print("这是前置条件")

    yield

    #后置清理
    print("这是后置条件")

@pytest.mark.demo
class TestDemo():
    """测试用例类"""

    def test_demo1(self,fixt):
        pass

    def test_demo2(self,fixt):
        pass

结果:两个用例,执行了2次

 

 

 

加scope=“class”

import pytest
#申明测试夹具
@pytest.fixture(scope="class")
def fixt():
    #前置条件
    print("这是前置条件")

    yield

    #后置清理
    print("这是后置条件")

@pytest.mark.demo
class TestDemo():
    """测试用例类"""

    def test_demo1(self,fixt):
        pass

    def test_demo2(self,fixt):
        pass

结果:一个测试类,只会执行一次测试夹具

 

 

 

除了上面2种形式,还有scope=“module”类型的

即,每个模块里面,即使有多个class,也只会执行一次前置、后置条件。

代码实现如下:

import pytest
#申明测试夹具 @pytest.fixture(scope
="function") def function_fixt(): #前置条件 print("这是function级别前置条件") yield #后置清理 print("这是function级别后置条件") #申明测试夹具 @pytest.fixture(scope="class") def class_fixt(): #前置条件 print("这是class级别前置条件") yield #后置清理 print("这是class级别后置条件")
#申明测试夹具 @pytest.fixture(scope
="module") def module_fixt(): #前置条件 print("这是module级别前置条件") yield #后置清理 print("这是module级别后置条件") @pytest.mark.demo class TestDemo(): """测试用例类""" def test_demo1(self,function_fixt,class_fixt,module_fixt): pass def test_demo2(self,function_fixt,class_fixt,module_fixt): pass @pytest.mark.demo class TestDemo2(): def test_demo3(self,function_fixt,class_fixt,module_fixt): pass

运行pytest -m "demo" -s,结果如下:

 

在实际的测试中,一个class测试类,添加一个class级别的测试夹具;不需要每个用例都打开、关闭一次浏览器,这样会降低测试效率。

fixture的基本内容,到这里全部描述完了。

下面再考虑个问题,在实际的测试工作中,注册、登录,取现等操作,每执行一次,就要打开/关闭一次浏览器,会比较麻烦,所以应该将fixture放在一个公共的文件中,让别的模块去调用即可。---conftest.py

 

2.conftest.py 文件

在项目路径下,直接新建一个py文件,命名为conftest.py。------注意:这个名字是固定的,作用:存储所有的fixture

①为什么不将测试夹具放在common模块中去?

  在因为driver这个对象,在测试用例中会经常的使用,放在common中要经常进行模块的import操作;而conftest是pytest里面有一个比较智能的地方,不需要去导入,运行程序的时候,会直接去conftest.py文件去找driver,找到了就继续运行,找不到就报错。省了很多导入的操作,也避免了因为导入模块可能存在路径错误的隐患。

代码实现:

"""固定文件名conftest.py,存储所有的测试夹具fixture"""
import pytest
from middware import handler

@pytest.fixture()
def driver():
    from selenium import webdriver

    ##前置条件
    #打开浏览器
    driver = webdriver.Chrome()

    # 设置隐性等待 等待的时间就可以放在config中,直接参数调用
    wait_time = handler.HandlerMiddle.yaml_data["selenium"]["wait_time"]
    driver.implicitly_wait(wait_time)

    yield driver

    #后置清理
    driver.quit()

test_login.py用例(无需导入conftest.py):

"""登录功能的测试用例"""

import pytest
from middware.handler import HandlerMiddle

data = HandlerMiddle.excel.read_data("login")

@pytest.mark.login
class TestLogin():
    """登录功能的测试类"""

@pytest.mark.smoke @pytest.mark.error @pytest.mark.parametrize("test_info",data) def test_login_error(self,test_info,driver): """登录失败测试步骤 1.打开浏览器 2.访问登录页面 3.元素定位+元素操作,输入用户名和密码,点击登录 4.通过获取页面内容得到实际结果,进行断言 :return: """ #2.访问登录页面 url = "http://120.78.128.25:8765/Index/login.html" driver.get(url) #3.元素定位+元素操作,输入用户名和密码,点击登录 driver.find_element_by_name("phone").send_keys(eval(test_info["data"])["username"]) #定位输入手机号为空 driver.find_element_by_name("password").send_keys(eval(test_info["data"])["password"])#定位输入的密码为空 driver.find_element_by_class_name("btn-special").click() #4.通过获取页面内容得到实际结果,进行断言 #实际结果是在页面上的提示,再次进行定位 actual_result = driver.find_element_by_class_name("form-error-info").text expected_result = test_info["expected_result"] #断言 assert actual_result == expected_result

运行run.py(同上面的run.py)即可完成测试login_error的用例。

 

3.重运行机制   -----多次运行

背景:为什么使用重运行机制?

    对于web自动化测试中,可能因为网速的问题导致测试不通过,所以要进行重新运行。(多次运行后,每次都失败,可能是bug)

①安装:pip install pytest-rerunfailures

②运行:pytest --reruns N (N表示rerun的次数)

              pytest --reruns N --reruns-delay M (M表示每运行一次,中间的间隔延迟时间)

 

举个栗子:

import pytest
@pytest.fixture(scope="function")
def function_fixt():
    #前置条件
    print("这是function级别前置条件")
    yield
    #后置清理
    print("这是function级别后置条件")

@pytest.mark.demo
class TestDemo():
    """测试用例类"""

    def test_demo1(self,function_fixt):
        assert 1==2  #断言不通过,进行rerun

    def test_demo2(self,function_fixt):
        pass

运行pytest -m "demo" --reruns 3,结果如下:

 

 运行:pytest -m "demo" --reruns 3 --reruns-delay 5   的结果如下:

 

 rerun3次,加首次运行,一共会运行4次。