目录
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次。