【pytest官方文件】解讀Skipping test functions,跳過測試用例詳解

把蘋果v咬哭發表於2021-04-21

有時候,為了滿足某些場景的需要,我們知道有些測試函式在這時候肯定不能執行,或者執行了也會失敗。那麼我們
可以選擇去跳過這個測試函式,這樣也就不會影響整體的測試函式執行效果,不至於在你執行的眾多綠色通過的測試
用例中,給你加點紅色的failed或者error。

舉個例子,有些測試函式只能在windows上執行,那麼換了個Linux平臺就不可以,就需要跳過這個測試函式。再比如,
有些測試用例會有一些外部資源的依賴,像資料庫,那麼當資料庫資源不可用的時候,也需要去跳過這個測試函式。

在pytest中提供了這樣一個機制來跳過測試函式。

一、skip

用法很簡單,只要在需要跳過的測試函式上面安上@pytest.mark.skip()即可。可以傳遞一個引數reason,填寫
跳過的理由,不填也可以。

@pytest.mark.skip(reason="沒啥理由,就不想執行")
def test_the_unknown():
    ...

執行結果:

test_skipif.py                                                          [100%]

============================= 1 skipped in 0.02s ==============================s
Skipped: 沒啥理由,就不想執行

Process finished with exit code 0

可以看到,這個測試用例被成功跳過,並且還有reason的輸出。不過pytest預設情況下,不會顯示跳過的測試函式的詳細資訊,
避免輸出與正常的混在一起,太亂。

二、pytest.skip(reason)

1. 在測試函式或者fixture函式裡使用

除了上述用法,還可以用pytest.skip(reason)函式,在測試函式中或者setup函式裡進行跳過。比如,當有些引數不符合你
的預期,那麼就跳過後面的執行。

上示例程式碼:

import pytest


@pytest.fixture()
def demo_fixture(request):
    test_input = request.param
    if test_input == 3:
        pytest.skip("傳入的值等於3就跳過執行")


@pytest.mark.parametrize("demo_fixture", [1, 3], indirect=True)
def test_the_unknown3(demo_fixture):
    ...


if __name__ == "__main__":
    pytest.main(["-s", '-r' "test_skipif.py"])

在上述程式碼裡,我在測試函式test_the_unknown3裡做了一個引數化,並且在fixture函式demo_fixture拿到這個引數。
按理來說,引數化了1和3,所以測試函式會執行2次。

我在fixture函式里加了判斷,當拿到的引數,等於3的時候,就跳過執行。所以最終的執行結果應該是,1執行,3跳過。

test_skipif.py                                                         [100%]

======================== 1 passed, 1 skipped in 0.09s =========================.s
Skipped: 傳入的值等於3就跳過執行

Process finished with exit code 0

結果符合預期。

這個用法,剛好解決了我一個實際問題。
那就是別的小組有不少人寫case用的測試資料,是會通過別的case或者別的介面呼叫後傳遞過來的,那麼當這些依賴有問題的時候
case執行就會有問題,導致誤報。如果使用pytest.skip(reason)這個函式,那麼可以有效緩解case誤報的問題。

2. allow_module_level=True跳過整個模組

如果你需要判斷某些條件符合時候,就要整個模組都跳過,就可以加上這個引數allow_module_level=True

import pytest

a = 1

if a != 3:
    pytest.skip("a不等於3就跳過整個檔案模組", allow_module_level=True)


@pytest.fixture()
def demo_fixture(request):
    test_input = request.param
    if test_input == 3:
        pytest.skip("傳入的值等於3就跳過")


@pytest.mark.parametrize("demo_fixture", [1, 3], indirect=True)
def test_the_unknown1(demo_fixture):
    ...


def test_the_unknown2():
    ...


def test_the_unknown3():
    ...

執行一下:

============================= 1 skipped in 0.02s ==============================
Skipped: a不等於3就跳過整個檔案模組

Process finished with exit code 0

三、skipif

1. skipif有條件的跳過

上面提到了在函式裡寫判斷,當滿足某個條件時通過pytest.skip函式來跳過,其實還可以直接用skipif,同樣可以
達到有條件地跳過某些內容的目的。

import sys


@pytest.mark.skipif(sys.version_info < (4, 0), reason="版本4.0以下就跳過執行")
def test_function():
    print(sys.version_info)

執行結果:

test_module1.py                                                         [100%]

============================= 1 skipped in 0.02s ==============================s
Skipped: 版本4.0以下就跳過執行

Process finished with exit code 0

2.模組之間共享skip標記

比如說,我現在有2個測試模組,分別是test_module1.pytest_module2.py
我在test_module1.py當中,定義一個skipif作為marker 共享,也就是not_equal_5
那麼在test_module2.py當中匯入這個marker,就可以直接使用了,看程式碼效果:

# content of test_module1.py
import pytest
import sys

version_judge = pytest.mark.skipif(
    sys.version_info < (4, 0), reason="版本4.0以下就跳過執行"
)

@version_judge
def test_the_unknown2():
    ...

if __name__ == "__main__":
    pytest.main(["-s", '-r' "test_module1.py"])

test_module2.py中匯入marker使用,執行後的預期結果,應該是test_the_unknown1跳過執行。

# content of test_module2.py
import pytest
from interface.demo.test_module1 import version_judge


@version_judge
def test_the_unknown1():
    ...


def test_the_unknown2():
    ...


if __name__ == "__main__":
    pytest.main(["-s", '-r' "test_module2.py"])

執行下test_module2.py:

test_module2.py                                                        [100%]

======================== 1 passed, 1 skipped in 0.08s =========================s
Skipped: 版本4.0以下就跳過執行
.
Process finished with exit code 0

四、跳過類或模組下的所有測試函式

1. 跳過類下的所有測試函式

如果把skipif放在類上,這個類下面的所有測試函式都會跳過。

import pytest
import sys

version_judge = pytest.mark.skipif(
    sys.version_info < (4, 0), reason="版本4.0以下就跳過執行"
)

@version_judge
class TestDemo():

    def test_the_unknown2(self):
        ...

    def test_the_unknown3(self):
        ...

def test_the_unknown1():
    ...

if __name__ == "__main__":
    pytest.main(["-s", '-r' "test_module1.py"])

執行結果,類TestDemo下的2個測試方法都會被跳過。

test_module1.py                                                       [100%]

======================== 1 passed, 2 skipped in 0.09s =========================s
Skipped: 版本4.0以下就跳過執行
s
Skipped: 版本4.0以下就跳過執行
.
Process finished with exit code 0

2.跳過模組下的所有測試函式

如果想跳過模組的所有測試函式,可以使用全域性變數pytestmark:

import pytest
import sys


pytestmark = pytest.mark.skipif(sys.version_info < (4, 0), reason="版本4.0以下就跳過執行")

class TestDemo():

    def test_the_unknown2(self):
        ...

    def test_the_unknown3(self):
        ...

def test_the_unknown1():
    ...

if __name__ == "__main__":
    pytest.main(["-s", '-r' "test_module1.py"])

執行,模組下的3個測試都會被跳過

test_module1.py                                                       [100%]

============================= 3 skipped in 0.02s ==============================s
Skipped: 版本4.0以下就跳過執行
s
Skipped: 版本4.0以下就跳過執行
s
Skipped: 版本4.0以下就跳過執行

Process finished with exit code 0

另外,如果多個skipif裝飾器應用於同一個測試函式,只要任何一個條件為真,該函式將被跳過。

五、跳過檔案或目錄

有時可能需要跳過一整個檔案或目錄。例如,有檔案裡的程式碼你不想去執行。在這種情況下,必須從pytest蒐集到的集合中排除檔案和目錄。
有關更多資訊,請參閱自定義測試集合,後續看情況單獨分享。

六、匯入依賴失敗跳過

當有些依賴的包匯入失敗的時候,可以通過pytest.importorskip這個函式來跳過。同樣,可以用在模組級別,fixture函式或者測試函式裡。

import pytest

# docutils = pytest.importorskip("docutils")

class TestDemo():

    def test_the_unknown2(self):
        ...

    def test_the_unknown3(self):
        pytest.importorskip("docutils")

def test_the_unknown1():
    ...

if __name__ == "__main__":
    pytest.main(["-s", '-r' "test_module1.py"])

執行結果,應該是跳過一個,執行2個:

test_module1.py                                                       [100%]

======================== 2 passed, 1 skipped in 0.09s =========================.s
Skipped: could not import 'docutils': No module named 'docutils'
.
Process finished with exit code 0

程式碼裡的docutils是一個第三方庫,你也可以根據庫的版本號跳過:

docutils = pytest.importorskip("docutils", minversion="0.3")

版本將從指定模組的__version__屬性中讀取,如果不符合條件,也會跳過。

七、總結

  1. 無條件跳過模組中的所有測試
pytestmark = pytest.mark.skip("all tests still WIP")
  1. 基於某些條件跳過模組中的所有測試
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
  1. 如果缺少某些匯入,則跳過模組中的所有測試
pexpect = pytest.importorskip("pexpect")

相關文章