从0到1上手Pytest
  qTGkBRdHYMul 2023年11月02日 30 0


引言

如果你想快速上手pytest,只关注"Pytest必会知识点"章节就可以了(该章节已经能够解决基础的ui和接口自动化测试需求);

如果你想要详细了解关于Fixture的使用方法,请关注“pytest高级用法”部分。

 

Pytest必会知识点

基础介绍

pytest 是 python 的第三方单元测试框架,比unittest 更简洁和高效,支持315种以上的插件,同时兼容nose、unittest 框架。

官网https://docs.pytest.org/en/latest/contents.html

安装pip install pytest

Pytest的命名规则

命名规则如下:

1. 测试文件应当命名为test_<something>.py或者_test.py

2. 测试函数、测试类方法应当命名为test_

3. 测试类应当命名为Test.

pytest的setup和teardown函数

1)模块级(setup_module/teardown_module)开始于模块始末

2)类级(setup_class/teardown_class)开始于类的始末

3)类里面的(setup/teardown)(运行在调用函数的前后)

4)功能级(setup_function/teardown_function)开始于功能函数始末(不在类中)

5)方法级(setup_method/teardown_method)开始于方法始末(在类中)

pytest的mark

pytest的mark主要用来标记用例,通过不同的标记实现不同的运行策略

主要用途:

    标记和分类用例: @pytest.mark.level1

    标记用例执行顺顺序pytest.mark.run(order=1) (需安装pytest-ordering)

    标记用例在指定条件下跳过或直接失败@pytest.mark.skipif()/xfail()

    标记使用指定fixture(测试准备及清理方法)@pytest.mark.usefixtures()

    参数化@pytest.mark.parametrize

    标记超时时间 @pytest.mark.timeout(60) (需安装pytest-timeout)

    标记失败重跑次数@pytest.mark.flaky(reruns=5, reruns_delay=1) (需安装pytest-rerunfailures)

Pytest运行用例

pytest 运行目录下的所有用例

pytest test_reg.py运行指定模块中的所有用例

pytest test_reg.py::TestClass::test_method 运行指定模块指定类指定用例

pytest -m tag 运行包含指定标签的所有用例

pytest -k "test_a and test_b"  运行名称包含指定表达式的用例(支持and or not)

其他常用参数

-q: 安静模式, 不输出环境信息

-v: 丰富信息模式, 输出更详细的用例执行信息

-s: 显示程序中的print/logging输出

Pytest的插件

pytest的插件也是通过pip install 命令进行安装,常用的插件如下

pytest-rerunfailures  用例重新执行

pytest-html 生成html报告

pytest-timeout 限制超时时间

pytest-parallel 多线程使用,常用配置命令如下:

 –workers (optional)  *:多进程运行需要加此参数,  *是进程数。默认为1。

 –tests-per-worker (optional)  *:多线程运行, *是每个worker运行的最大并发线程数。默认为1

实例:多线程执行并生成html报告

定义两个py文件test_demo1.py和test_demo2.py并放入同一包中

#test_demo1.py

 

从0到1上手Pytest_html

#test_demo2.py

 

从0到1上手Pytest_测试用例_02

在包中运行脚本

pytest -v --tests-per-worker 2  --html=report.html

可以看到两个py文件中的用例全部被执行,并在该目录下生成了report.html测试报告,测试结果如下:

====================================================================================================================test session starts =====================================================================================================================
platformwin32 -- Python 3.7.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 --c:\python37\python.exe
cachedir:.pytest_cache
metadata:{'Python': '3.7.4', 'Platform': 'Windows-10-10.0.17763-SP0', 'Packages':{'pytest': '5.3.2', 'py': '1.8.1', 'pluggy': '0.13.1'}, 'Plugins': {'forked':'1.1.3', 'html': '2.0.1', 'metadata': '1.8.0', 'parallel': '0.0.10','rerunfailures': '8.0', '
xdist':'1.31.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_151'}
rootdir:C:\Users\Kevin\NewLesson\pytest_demo2
plugins:forked-1.1.3, html-2.0.1, metadata-1.8.0, parallel-0.0.10, rerunfailures-8.0,xdist-1.31.0
collected6 items                                                                                                                                                                                                                                            
pytest-parallel:1 worker (process), 2 tests per worker (threads)
 
 
test_demo1.py::TestPrint::test_test1test_demo1.py::TestPrint::test_test2
test_demo1.py::TestPrint::test_test1PASSED
test_demo1.py::TestPrint::test_test3
test_demo1.py::TestPrint::test_test2FAILED
test_demo2.py::TestPrint::test_test11
test_demo1.py::TestPrint::test_test3PASSED
test_demo2.py::TestPrint::test_test21
test_demo2.py::TestPrint::test_test11PASSED
test_demo2.py::TestPrint::test_test31
test_demo2.py::TestPrint::test_test21FAILED
test_demo2.py::TestPrint::test_test31PASSED
 
==========================================================================================================================FAILURES==========================================================================================================================
____________________________________________________________________________________________________________________TestPrint.test_test2____________________________________________________________________________________________________________________
 
self=
 
    def test_test2(self):
        time.sleep (2)
>       assert (1 == 2)
E       assert 1 == 2
E         -1
E         +2
 
test_demo1.py:16:AssertionError
-------------------------------------------------------------------------------------------------------------------Captured stdout setup--------------------------------------------------------------------------------------------------------------------
setupsetup
 
------------------------------------------------------------------------------------------------------------------Captured stdout teardown------------------------------------------------------------------------------------------------------------------
teardown
___________________________________________________________________________________________________________________TestPrint.test_test21____________________________________________________________________________________________________________________
 
self=
 
    @pytest.mark.smoke
    def test_test21(self):
        time.sleep (2)
>       assert (1 == 2)
E       assert 1 == 2
E         -1
E         +2
 
test_demo2.py:16:AssertionError
-------------------------------------------------------------------------------------------------------------------Captured stdout setup--------------------------------------------------------------------------------------------------------------------
setup
------------------------------------------------------------------------------------------------------------------Captured stdout teardown------------------------------------------------------------------------------------------------------------------
teardown
======================================================================================================================warnings summary======================================================================================================================
c:\python37\lib\site-packages\_pytest\mark\structures.py:327
 c:\python37\lib\site-packages\_pytest\mark\structures.py:327:PytestUnknownMarkWarning: Unknown pytest.mark.smoke - is this a typo?  You can register custom marks to avoid thiswarning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,
 
--Docs: https://docs.pytest.org/en/latest/warnings.html
---------------------------------------------------------------------------------------generated html file: file://C:\Users\Kevin\NewLesson\pytest_demo2\report.html----------------------------------------------------------------------------------------
===========================================================================================================2 failed, 4 passed, 1 warning in 6.42s===========================================================================================================

pytest高级用法

Fixture

fixture用途

备注:这里主要引用

1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现

2.测试用例的前置条件可以使用fixture实现

3.支持经典的xunit fixture ,像unittest使用的setup和teardown

fixture区别于unnitest的传统单元测试(setup/teardown)有显著改进:

有独立的命名,并通过声明它们从测试函数、模块、类或整个项目中的使用来激活。

按模块化的方式实现,每个fixture都可以互相调用。

fixture的范围从简单的单元测试到复杂的功能测试,可以对fixture配置参数,或者跨函数function,类class,模块module或整个测试session范围。

可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题

fixture的使用

Fixture名字作为用例的参数

fixture的名字直接作为测试用例的参数,上面的实例就这这种方式,再来看一个实例

# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
   return'fixtureFunc'
 
def test_fixture(fixtureFunc):
   print('我调用了{}'.format(fixtureFunc))
 
class TestFixture(object):
   def test_fixture_class(self,fixtureFunc):
        print('在类中使用fixture"{}"'.format(fixtureFunc))
 
if__name__=='__main__':
   pytest.main(['-v','test_fixture.py'])

使用@pytest.mark.usefixtures('fixture')装饰器

每个函数或者类前使用@pytest.mark.usefixtures('fixture')装饰器装饰

实例

# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
   print('\nfixture->fixtureFunc')
@pytest.mark.usefixtures('fixtureFunc')
def test_fixture():
   print('in test_fixture')
@pytest.mark.usefixtures('fixtureFunc')
class TestFixture(object):
   def test_fixture_class(self):
        print('in class with text_fixture_class')
 
if__name__=='__main__':
   pytest.main(['-v','test_fixture.py'])

使用autouse参数

指定fixture的参数autouse=True这样每个测试用例会自动调用fixture(其实这里说的不是很准确,因为还涉及到fixture的作用范围,那么我们这里默认是函数级别的,后面会具体说fixture的作用范围)

实例

# test_fixture.py
import pytest
@pytest.fixture(autouse=True)
def fixtureFunc():
   print('\nfixture->fixtureFunc')
def test_fixture():
   print('intest_fixture')
class TestFixture(object):
   def test_fixture_class(self):
        print('in class with text_fixture_class')
 
if__name__=='__main__':
   pytest.main(['-v','test_fixture.py'])

结果

 fixture->fixtureFunc

.intest_fixture

 

 fixture->fixtureFunc

.inclasswithtext_fixture_class

                                                      [100%]

 

========================== 2 passedin0.04 seconds ===========================

从结果可以看到每个测试用例执行前都自动执行了fixture

 

fixture作用范围

上面所有的实例默认都是函数级别的,所以测试函数只要调用了fixture,那么在测试函数执行前都会先指定fixture。说到作用范围就不得不说fixture 的第二个参数scope参数。

scope参数可以是session, module,class,function;默认为function

1.session 会话级别(通常这个级别会结合conftest.py文件使用,所以后面说到conftest.py文件的时候再说)

2.module 模块级别: 模块里所有的用例执行前执行一次module级别的fixture

3.class 类级别 :每个类执行前都会执行一次class级别的fixture

4.function :前面实例已经说了,这个默认是默认的模式,函数级别的,每个测试用例执行前都会执行一次function级别的fixture

下面我们通过一个实例具体看一下 fixture的作用范围

# test_fixture.py

import pytest
@pytest.fixture(scope='module', autouse=True)
def module_fixture():
   print('\n-----------------')
   print('我是module fixture')
   print('-----------------')
@pytest.fixture(scope='class')
def class_fixture():
   print('\n-----------------')
   print('我是class fixture')
   print('-------------------')
@pytest.fixture(scope='function', autouse=True)
def func_fixture():
   print('\n-----------------')
   print('我是function fixture')
   print('-------------------')
def test_1():
   print('\n 我是test1')
 
@pytest.mark.usefixtures('class_fixture')
class TestFixture1(object):
   def test_2(self):
        print('\n我是class1里面的test2')
   def test_3(self):
        print('\n我是class1里面的test3')
@pytest.mark.usefixtures('class_fixture')
class TestFixture2(object):
   def test_4(self):
        print('\n我是class2里面的test4')
   def test_5(self):
        print('\n我是class2里面的test5')
if__name__=='__main__':
   pytest.main(['-v','--setup-show','test_fixture.py'])

fixture实现teardown

其实前面的所有实例都只是做了测试用例执行之前的准备工作,那么用例执行之后该如何实现环境的清理工作呢?这不得不说yield关键字了,相比大家都或多或少的知道这个关键字,他的作用其实和return差不多,也能够返回数据给调用者,唯一的不同是被掉函数执行遇到yield会停止执行,接着执行调用处的函数,调用出的函数执行完后会继续执行yield关键后面的代码。看下下面的实例来了解一下如何实现teardown功能

import pytest
from selenium import webdriver
import time
@pytest.fixture()
def fixtureFunc():
'''实现浏览器的打开和关闭'''
   driver=webdriver.Firefox()
   yield driver
   driver.quit()
def test_search(fixtureFunc):
   '''访问百度首页,搜索pytest字符串是否在页面源码中'''
   driver=fixtureFunc
   driver.get('http://www.baidu.com')
   driver.find_element_by_id('kw').send_keys('pytest')
   driver.find_element_by_id('su').click()
   time.sleep(3)
   source=driver.page_source
   assert 'pytest' insource
 
if__name__=='__main__':
   pytest.main(['--setup-show','test_fixture.py'])

这个实例会先打开浏览器,然后执行测试用例,最后关闭浏览器。大家可以试试!  通过yield就实现了 用例执行后的teardown功能

 

conftest.py

1、可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture

2、conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件

3、不需要import导入 conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了,如果放到某个package下,那就在改package内有效,可有多个conftest.py

4、conftest.py配置脚本名称是固定的,不能改名称

5、conftest.py文件不能被其他文件导入

6、所有同目录测试文件运行前都会执行conftest.py文件

主要用途之一共享变量,代码如下:

import pytest
# conftest.py
@pytest.fixture(scope='session')
def get_token():
    token= 'abcd'
    return token

这样,在多个测试用例中使用fixture get_token()时就可以共享 token值了。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
qTGkBRdHYMul