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")