pytest學習總結


 官方pytest文檔:Full pytest documentation — pytest documentation


 

 

一、pytest以及輔助插件的安裝

1、pytest安裝

pip install pytest

2、輔助插件及介紹

pytest-html(生成htm報告的插件)
pytest-xdist (多線程運行的插件)
pytest-ordering (改變用例的執行順序的插件)
pytest-rerunfailres (失敗用例重跑的插件)
allure-pytest (生成美觀自定義的allure報告)pytest test_*.py --alluredir=result + allure serve result
        allure generate --alluredir=result -o report/ --clean
pytest-rerunfailures (失敗重跑插件)
pytest-repeat   (重復執行用例的插件)
pytest-assume  (assume多重斷言,斷言失敗后邊的代碼會繼續執行)

//插件太多,可通過pip install -r pludin.txt一次性下載完成
例如:plugin.txt中內容如下
pytest-html
pytest-xdist
終端運行:pip install -r pugin.txt即可下載這兩個插件

 3、安裝插件報錯

1)下面是沒有安裝相應插件報錯,例:沒有安裝allure-pytest插件,運行pytest --alluredir=./temp 會報此錯

ERROR: usage: run.py [options] [file_or_dir] [file_or_dir] [...]
run.py: error: unrecognized arguments: --alluredir=./reports/temp/ --clean-alluredir
  inifile: C:\Users\EDY\PycharmProjects\ProductAuto\pytest.ini
  rootdir: C:\Users\EDY\PycharmProjects\ProductAuto

 

 

二、pytest運行

 測試套件默認規則:測試文件必須以test_*開頭,測試類名字必須是Test*開頭,測試方法名字必須是test_*開頭,否則運行時會找不到測試套件

pytest命令的參數:

-vs        輸出詳細信息。輸出調試信息。如: pytest -vs
-n      多線程運行。( 前提安裝插件: pytest-xdist)如: pytest -VS -n=2 --reruns 失敗重跑(前提安裝插件: pytest-rerunfailres )如: pytest --reruns=2    raise Exception()拋出異常,try except解決異常。 -x     出現一個用例失敗則停止測試。如: pytest -x --maxfail 出現幾個失敗才終止,如: pytest --maxfail=2 --html   生成html的測試報告(前提安裝插件: pytest-html) , 如: pytest --html=report/report.html -k     運行測試用例名稱中包含某個字符串的測試用例。 -m     只測試被標記的用例
--strict-markers 未在pytest.ini配置文件注冊的標記mark都會引發報錯
--reruns n  (安裝pytest-rerunfailures插件),失敗重跑n次,最大運行次數n pytest --reruns 5
--reruns-delay n )(pytest-rerunfailures插件),pytest --rerun 2 --reruns-delay 5 失敗重跑之間間隔5s
--count    重復執行測試,需安裝pytest-repeat,使用:pytest --count=5或者pytest --count 5
        重復執行所有測試用例5次,主要結合-x使用,測試偶發bug,運行直到執行失敗
        還有@pytest.mark.repeat(n)可對測試類、方法使用

有兩種執行方式:

1)命令行運行:pytest -vs -x

2)主函數運行:

if __name__ == "__main__":
    pytest.main(["-vs”,“-x"])

指定運行:

pytest  test_*.py    //運行指定的test_*.py ,多個py文件分號隔開
pytest  testcase/    //運行指定的testcase目錄下所有測試文件    
pytest -k "cs”    //測試包含有“cs”的測試類和方法
pytest  testcase/test_*.py::Test_lei::test_* //可具體指定某個方法

測試執行結果:

  • 退出code 0: 收集並成功通過所有測試用例
  • 退出code 1: 收集並運行了測試,部分測試用例執行失敗
  • 退出code 2: 測試執行被用戶中斷
  • 退出code 3: 執行測試中發生內部錯誤
  • 退出code 4: pytest命令行使用錯誤
  • 退出code 5: 沒有收集到測試用例

 

全局配置文件pytest.ini,一般建在項目根目錄下

注意:

1)這個配置文件的名稱不能改

2)編碼格式是ANSI,寫入中文后自動變為GB2312 中文簡體

3)pytest.main()會帶上其中配置的參數

pytest.ini配置文件內容解釋,實際中根據需求可選擇性添加配置。

[pytest]
//pytest參數配置 addopts = -VS
//需要執行用例模塊目錄 testpaths =./testcases
//可更改測試文件、類、方法的默認規則,一般不做更改 python_files = test_*.py python_classes = Test* python_functions = test_*

//標記用例,一般用於冒煙測試,以下的smoke是標簽名,”冒煙測試“是描述,使用
markers=
  smoke:"冒煙測試"
  flow:"流水"

//與@pytest.mark.xfail()的strict一樣,為False,意外pass的結果顯示xpass,為True,則顯示Failed
xfail_strict = False

//控制台日志輸出控制器,為True輸出,為False關閉
log_cli = True

//用來加速收集用例,去掉不要諞歷的目錄
norecursedirs = * (目錄)

 

斷言類型

1)assert正常斷言

def test_001(self):  
  
assert var==3  //判斷var等於3
   assert var in ‘ab’  //判斷字符串‘ab’包含var
   assert var is False  //判斷var 為假
   assert var not True  //判斷var不為真
   assert var  //判斷var為真

2)pytest.raise()異常斷言

def test_002(self):
    with pytest.raises(ZeroDivisionError):
        1/0    

3)pytest.warns()警示斷言

暫沒有合適例子

 

臨時目錄

1)項目運行過程中,總會產生一些臨時文件,pytest提供了tmp_path來創建臨時目錄。

tmp_path是一個pathlib/pathlib2.Path對象。以下是測試使用方法的示例如:

# test_tmp_path.py文件內容
import os

CONTENT = u"content"

def test_create_file(tmp_path):
    d = tmp_path / "sub"
    d.mkdir()
    p = d / "hello.txt"
    p.write_text(CONTENT)
    assert p.read_text() == CONTENT
    assert len(list(tmp_path.iterdir())) == 1
    assert 0

測試結果如下:除了assert0以外,其他都斷言成功
_____________________________ test_create_file _____________________________ tmpdir = local('PYTEST_TMPDIR/test_create_file0') def test_create_file(tmpdir): p = tmpdir.mkdir("sub").join("hello.txt") p.write("content") assert p.read() == "content" assert len(tmpdir.listdir()) == 1 > assert 0 E assert 0

2)tmpdir_factory

 

四、前置和后置

前置和后置方法如下:

  • 模塊級別:setup_module、teardown_module
  • 函數級別:setup_function、teardown_function,不在類中的方法
  • 類級別:setup_class、teardown_class
  • 方法級別:setup_method、teardown_method
  • 方法細化級別:setup、teardown
def setup_module():
    print("=====整個.py模塊開始前只執行一次:打開瀏覽器=====")

def teardown_module():
    print("=====整個.py模塊結束后只執行一次:關閉瀏覽器=====")

def setup_function():
    print("===每個函數級別用例開始前都執行setup_function===")

def teardown_function():
    print("===每個函數級別用例結束后都執行teardown_function====")

def test_one():
    print("one")

class TestCase():
    def setup_class(self):
        print("====整個測試類開始前只執行一次setup_class====")

    def teardown_class(self):
        print("====整個測試類結束后只執行一次teardown_class====")

    def setup_method(self):
        print("==類里面每個用例執行前都會執行setup_method==")

    def teardown_method(self):
        print("==類里面每個用例結束后都會執行teardown_method==")

    def setup(self):
        print("=類里面每個用例執行前都會執行setup=")

    def teardown(self):
        print("=類里面每個用例結束后都會執行teardown=")

    def test_three(self):
        print("two")

 

五、fixtrue實現前后置

@pytest.fixtrue(scope="xxx")和yeild實現前置和后置

1)參數scope的五個范圍級別:fuction(默認)、class、module、package、session,實例化順序級別從右至左

2)類調用fixtrue固件只能通過@pytest.mark.usefixtrues("aaa","bbb"),aaa和bbb均為函數名

3)方法能通過參數調用,如def aaa(bbb),aaa為調用函數,bbb為固件函數名,也能使用@pytest.mark.usefixtrues("aaa","bbb")調用

具體效果如下:

//不調用則不顯示
@pytest.fixture()
def ttt():
    print("fuction-setup4")

@pytest.fixture()
def aaa():
    print("fuction-setup1")

@pytest.fixture()
def bbb():
    print("fuction-setup2")

@pytest.fixture(scope="class")
def ccc():
    print("setup")     //調用程序前執行
    yield  //后面的的方法體相當於后置
    print("teardown")    //調用程序后執行

//類調用fuction范圍級別固件bbb,相當於類里面的每個方法都調用bbb
@pytest.mark.usefixtures("bbb")
//類調用class級別固件ccc,只在調用的類前執行一次
@pytest.mark.usefixtures("ccc")
class Test_study:

    def test_ddd(self,aaa):    //先bbb輸出,再aaa輸出
        print("AAAAAA")

    @pytest.mark.smoke
    def test_eee(self):
        print("BBBBBB")


-------------------結果如下--------------------------------

kuadi/test_kuadi_login.py::Test_study::test_ddd setup
fuction-setup2
fuction-setup1
AAAAAA
PASSED
kuadi/test_kuadi_login.py::Test_study::test_eee fuction-setup2
BBBBBB
PASSEDteardown

4)其他調用情況

@pytest.fixture(scope="class")
def aaa():
    print("aaa_start")
    yield
    print("aaa_end")

@pytest.fixture()
def bbb():
    print("bbb_start")
    yield
    print("bbb_end")

@pytest.mark.usefixtures("bbb")
def test_fff():
    print("fff")

class Test_QQQ:
    @pytest.mark.usefixtures("bbb")
    def test_ccc(self):
        print("ccc")

    def test_ddd(self, aaa):
        print("ddd")

    def test_eee(self):
        print("eee")

結果------------
fds.py::test_fff bbb_start
fff
PASSEDbbb_end

fds.py::Test_QQQ::test_ccc bbb_start
ccc
PASSEDbbb_end

fds.py::Test_QQQ::test_ddd aaa_start
ddd
PASSED
fds.py::Test_QQQ::test_eee eee
PASSEDaaa_end

 

5)yield和addfinalizer函數的使用

//yield配合fixtrue實現前后置方法
@pytest.fixture(scope="class")
def ccc():
    print("setup")
    yield
    print("teardown")

//with配合yield,等待測試完成后,自動關閉連接
@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
        yield smtp_connection

//通過后置操作在測試完成后關閉瀏覽器連接
@pytest.fixture(scope="module")
def test_addfinalizer(request):
    # 前置操作setup
    print("==再次打開瀏覽器==")
    test = "test_addfinalizer"

    def fin():
        # 后置操作teardown
        print("==再次關閉瀏覽器==")

    request.addfinalizer(fin)
    # 返回前置操作的變量
    return test

 

六、conftest文件

conftest文件注意事項:

  • pytest會默認讀取conftest.py里面的所有fixture,所以不用導入fixtrue
  • conftest.py 文件名稱是固定的,不能改動
  • conftest.py中fixtrue只對同一個package下的所有測試用例生效,其他目錄中引入會報錯not found
  • 不同目錄可以有自己的conftest.py,一個項目中可以有多個conftest.py
  • 下級目錄的conftest中fixtrue可以覆蓋重寫上級目錄中同名的fixtrue,且就近生效
  • 測試用例文件中不需要手動import conftest.py,pytest會自動查找

 

七、參數化

@pytest.mark.parametrize()

官方文檔:How to parametrize fixtures and test functions — pytest documentation

用法如下:

//測試方法和函數使用
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)]) def test_eval(test_input, expected): assert eval(test_input) == expected
//測試類使用
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected

    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected
//模塊全局使用
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])

class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected
//mark標記使用
@pytest.mark.parametrize(
    "test_input,expected",
    [("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected
//疊加使用
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    print({x},{y})

結果按順序:0,2/1,2/0.3/1,3

 

@pytest.fixtrue()

第一個例子
#
content of conftest.py import pytest import smtplib @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"]) def smtp_connection(request): smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) yield smtp_connection print("finalizing {}".format(smtp_connection)) smtp_connection.close()
//第二個例子
@pytest.fixture(params=[0, 1], ids=["spam", "ham"]) def a(request): return request.param def test_a(a): pass def idfn(fixture_value): if fixture_value == 0: return "eggs" else: return None @pytest.fixture(params=[0, 1], ids=idfn) def b(request): return request.param def test_b(b): pass 結果------------------------------------------------- <Function test_a[spam]> <Function test_a[ham]> <Function test_b[eggs]> <Function test_b[1]>

@pytest.fixtrue帶標記

@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
    return request.param

def test_data(data_set):
    pass

fixtrue中使用fixtrue:smtp_connection()是第一個用例中fixtrue

class App:
    def __init__(self, smtp_connection):
        self.smtp_connection = smtp_connection

@pytest.fixture(scope="module")
def app(smtp_connection):
    return App(smtp_connection)

def test_smtp_connection_exists(app):
    assert app.smtp_connection

按fix true實例自動對測試進行分組

@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
    param = request.param
    print("  SETUP modarg", param)
    yield param
    print("  TEARDOWN modarg", param)


@pytest.fixture(scope="function", params=[1, 2])
def otherarg(request):
    param = request.param
    print("  SETUP otherarg", param)
    yield param
    print("  TEARDOWN otherarg", param)


def test_0(otherarg):
    print("  RUN test0 with otherarg", otherarg)


def test_1(modarg):
    print("  RUN test1 with modarg", modarg)


def test_2(otherarg, modarg):
    print("  RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg))

--------------------------------結果-------------------------------
collecting ... collected 8 items

test_module.py::test_0[1]   SETUP otherarg 1
  RUN test0 with otherarg 1
PASSED  TEARDOWN otherarg 1

test_module.py::test_0[2]   SETUP otherarg 2
  RUN test0 with otherarg 2
PASSED  TEARDOWN otherarg 2

test_module.py::test_1[mod1]   SETUP modarg mod1
  RUN test1 with modarg mod1
PASSED
test_module.py::test_2[mod1-1]   SETUP otherarg 1
  RUN test2 with otherarg 1 and modarg mod1
PASSED  TEARDOWN otherarg 1

test_module.py::test_2[mod1-2]   SETUP otherarg 2
  RUN test2 with otherarg 2 and modarg mod1
PASSED  TEARDOWN otherarg 2

test_module.py::test_1[mod2]   TEARDOWN modarg mod1
  SETUP modarg mod2
  RUN test1 with modarg mod2
PASSED
test_module.py::test_2[mod2-1]   SETUP otherarg 1
  RUN test2 with otherarg 1 and modarg mod2
PASSED  TEARDOWN otherarg 1

test_module.py::test_2[mod2-2]   SETUP otherarg 2
  RUN test2 with otherarg 2 and modarg mod2
PASSED  TEARDOWN otherarg 2
  TEARDOWN modarg mod2

測試功能不需要直接訪問fixtrue時,如建一個臨時目錄或者文件供測試使用,結合@pytest.markusefixtrues()

# content of conftest.py 

import os
import tempfile

import pytest


@pytest.fixture
def cleandir():
    with tempfile.TemporaryDirectory() as newpath:
        old_cwd = os.getcwd()
        os.chdir(newpath)
        yield
        os.chdir(old_cwd)

------------------------------------
# content of test_setenv.py
import os
import pytest


@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
    def test_cwd_starts_empty(self):
        assert os.listdir(os.getcwd()) == []
        with open("myfile", "w") as f:
            f.write("hello")

    def test_cwd_again_starts_empty(self):
        assert os.listdir(os.getcwd()) == []
//其他用法
@pytest.mark.usefixtures("cleandir", "anotherfixture")  指定多個fix true
def test():
    pass

//使用pytestmark在測試模塊級別指定夾具的用途
pytestmark = pytest.mark.usefixtures("cleandir")

//將項目中所有測試所需的夾具放入ini文件中
[pytest]
usefixtures = cleandir

結合@pytest.mark.parametrize()使用,fixtrue作測試數據處理功能,如:factory

@pytest.fixture(scope="function")
def input_user(request):
    user = request.param
    print("登錄賬戶:%s" % user)
    return user

@pytest.fixture(scope="function")
def input_psw(request):
    psw = request.param
    print("登錄密碼:%s" % psw)
    return psw

name = ["name1", "name2"]
pwd = ["pwd1", "pwd2"]

@pytest.mark.parametrize("input_user", name, indirect=True)
@pytest.mark.parametrize("input_psw", pwd, indirect=True)
def test_more_fixture(input_user, input_psw):
    print("fixture返回的內容:", input_user, input_psw)

fixtrue會覆蓋同名的fixtrue

tests/
    __init__.py

    conftest.py
        # content of tests/conftest.py
        import pytest

        @pytest.fixture(params=['one', 'two', 'three']) //最初的參數化fixtrue
        def parametrized_username(request):
            return request.param

        @pytest.fixture  //最初的非參數化fixtrue
        def non_parametrized_username(request):  
            return 'username'

    test_something.py
        # content of tests/test_something.py
        import pytest

        @pytest.fixture  //非參數化fixtrue覆蓋參數化fixtrue
        def parametrized_username():
            return 'overridden-username'

     //參數化fixtrue覆蓋非參數化fixtrue @pytest.fixture(params
=['one', 'two', 'three']) def non_parametrized_username(request): return request.param def test_username(parametrized_username): assert parametrized_username == 'overridden-username' def test_parametrized_username(non_parametrized_username): assert non_parametrized_username in ['one', 'two', 'three'] test_something_else.py # content of tests/test_something_else.py def test_username(parametrized_username): assert parametrized_username in ['one', 'two', 'three'] def test_username(non_parametrized_username): assert non_parametrized_username == 'username'

直接參數化的fixtrue覆蓋非參數化fixtrue

//直接參數化也可以覆蓋fixtrue
tests/ __init__.py conftest.py # content of tests/conftest.py import pytest @pytest.fixture def username(): return 'username' test_something.py # content of tests/test_something.py import pytest @pytest.mark.parametrize('username', ['directly-overridden-username']) def test_username(username): assert username == 'directly-overridden-username'

 

 

八、其他裝飾器

官方API Reference — pytest documentation

1、@pytest.mark.xfail()、pytest.xfail

@pytest.mark.xfail(condition,*,reason=none,raises=none,run=True,strict=False)

1)raises:none表示默認匹配所有異常錯誤,值為type(Exception),只能是異常值

2)condition:boolea or str類型,就是有條件執行,只跳過部分特殊用例時使用

3)run:為true,該怎么樣就怎么樣,為False,則該測試套件不會運行,且始終標為xfail

4)reson:原因說明

5)strict:為False,用例通過顯示xpass,用例失敗顯示xfail,為True,用例失敗顯示xfail,用例意外通過則顯示fail

函數、方法、類執行前使用@pytest.mark.xfail(),預期失敗

對方法使用:

//raises參數為空或者與所報的錯誤匹配時結果是xfail,此結果是預期失敗,說明用例通過
   @pytest.mark.xfail()   
    def test_002(self):
        print("賬戶修改")
        raise Exception

//raises參數與所報的錯誤不匹配時結果是failed
    @pytest.mark.xfail(raises=ZeroDivisionError)
    def test_004(self):
        print("賬戶修改")
        raise Exception

--------------------結果------------------------

test_interface.py::Test_kuaidi::test_002 賬戶修改
XFAIL
test_interface.py::Test_kuaidi::test_004 賬戶修改
FAILED

對類使用:

@pytest.mark.xfail(raises=ZeroDivisionError)
class Test_kuaidi:

    def test_002(self):  //不匹配的用例顯示failed
        print("賬戶修改")
        raise Exception

    def test_004(self):    //xpass,意外通過
        print("賬戶修改")
        # raise Exception

    def test_005(self):    //匹配的用例顯示xfail
        1/0

------------------------結果展示---------------

test_interface.py::Test_kuaidi::test_002 賬戶修改
FAILED
test_interface.py::Test_kuaidi::test_004 賬戶修改
XPASS
test_interface.py::Test_kuaidi::test_005 XFAIL

pytest.xfail(reason=none),主要使用來對已知錯誤和功能缺失使用的,使用例子:

def test_aaa():
    pytest.xfail(reason="功能未做")

---------結果-------------------------

test_interface.py::test_aaa XFAIL (功能未做)

 

2、@pytest.mark.skip()、@pytest.mark.skipif()、pytest.skip、pytest.importorskip

都是跳過用例,區別:

@pytest.mark.skip(reason=None):reason跳過的原因,不影響程序運行

@pytest.mark.skipif(condition, reason=None):condition參數的值只能是Boolean或者str,有條件跳過

@pytest.mark.skip使用在測試類、測試方法、測試函數上,則測試類、測試方法、測試函數中所有用例都會跳過

@pytest.mark.skipif使用在測試類、測試方法、測試函數上,condition有值則根據條件跳過,沒有值和@pytest.mark.skip一樣

@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows")
class TestSkipIf(object):
    def test_function(self):
        print("不能在window上運行")

 pytest.skip(reason=none,allow_module_level=False,msg)

allow_module_level:默認為False,為True則會允許模塊調用,執行時會跳過模塊中剩余的用例

def test_aaa():
    pytest.skip("功能未做") //注意,pytest.skip(reason="功能未做")會報錯

結果:test_interface.py::test_aaa SKIPPED (功能未做)

pytest.skip(r"功能未做",allow_module_level=True)

結果:跳過所有剩余用例

pytest.importorskip(modname,minversion=none,reason=none)

作用:缺少導入包,跳過用例測試

參數:modname,導入的模塊名;minversion,模塊最小版本號

expy = pytest.importorskip("pytest", minversion="7.0",reason="pytest7.0版本沒有出來")

@expy
def test_import():
    print("test")
結果:會跳過所有該模塊的用例測試

 

3、@pytest.mark.flaky()

前提安裝pytest-rerunfailures,reruns:重跑次數,reruns_delay:重跑間隔時間

禁止:1、不能和@pytest.fixtrue()一起使用 

   2、該插件與pytest-xdist的looponfail不兼容

   3、與核心--pdb標志不兼容

    @pytest.mark.flaky(reruns=2,reruns_delay=5)
    def test_ddd(self):
        assert False

---------------------執行結果-------------------------
kuadi/test_kuadi_login.py::Test_study::test_ddd RERUN
kuadi/test_kuadi_login.py::Test_study::test_ddd RERUN
kuadi/test_kuadi_login.py::Test_study::test_ddd FAILED

 

 

九、hooks函數

學習地址:Pytest權威教程21-API參考-04-鈎子函數(Hooks) - 韓志超 - 博客園 (cnblogs.com)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM